Skip to content

feat: added art/grid API endpoint #46

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions src/raw/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,54 @@ impl Client {

ResponseType::from_response(response)
}

/// Endpoint: [`art/grid/`](https://listenbrainz.readthedocs.io/en/latest/users/api/art.html#post--1-art-grid-)
/// ```no_run
/// use listenbrainz::raw::Client;
/// use listenbrainz::raw::request::ArtGrid;
/// use listenbrainz::raw::request::ArtGridBackground;
///
/// let grid = ArtGrid {
/// background: ArtGridBackground::Transparent,
/// image_size: 750,
/// dimension: 4,
/// skip_missing: false,
/// show_caa: true,
/// tiles: Some(vec![
/// "0,1,4,5".to_string(),
/// "10,11,14,15".to_string(),
/// "2".to_string(),
/// "3".to_string(),
/// "6".to_string(),
/// "7".to_string(),
/// "8".to_string(),
/// "9".to_string(),
/// "12".to_string(),
/// "13".to_string()
/// ]),
/// release_mbids: Some(vec![
/// "d101e395-0c04-4237-a3d2-167b1d88056c".to_string(),
/// "4211382c-39e8-4a72-a32d-e4046fd96356".to_string(),
/// "6d895dfa-8688-4867-9730-2b98050dae04".to_string(),
/// "773e54bb-3f43-4813-826c-ca762bfa8318".to_string(),
/// "ec782dbe-9204-4ec3-bf50-576c7cf3dfb3".to_string(),
/// "10dffffc-c2aa-4ddd-81fd-42b5e125f240".to_string(),
/// "be5f714d-02eb-4c89-9a06-5e544f132604".to_string(),
/// "3eee4ed1-b48e-4894-8a05-f535f16a4985".to_string()
/// ]),
/// release_group_mbids: None,
/// cover_art_size: None
/// };
///
/// println!("{:#?}", Client::new().art_grid(grid).unwrap());
/// ```
pub fn art_grid(&self, body: ArtGrid) -> Result<ArtGridResponse, Error> {
let endpoint = format!("{}{}", self.api_root_url, Endpoint::ArtGrid);

let response = attohttpc::post(endpoint).json(&body)?.send()?;

ResponseType::from_response(response)
}
}

impl Default for Client {
Expand Down
2 changes: 2 additions & 0 deletions src/raw/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub enum Endpoint<'a> {
UserFollowing(&'a str),
UserUnfollow(&'a str),
UserFollow(&'a str),
ArtGrid,
}

impl<'a> fmt::Display for Endpoint<'a> {
Expand Down Expand Up @@ -74,6 +75,7 @@ impl<'a> fmt::Display for Endpoint<'a> {
Self::UserFollowing(user) => write!(f, "user/{}/following", user),
Self::UserUnfollow(user) => write!(f, "user/{}/unfollow", user),
Self::UserFollow(user) => write!(f, "user/{}/follow", user),
Self::ArtGrid => write!(f, "art/grid"),
}
}
}
84 changes: 84 additions & 0 deletions src/raw/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,87 @@ impl Borrow<str> for Empty {
unreachable!("Should never be used as a value")
}
}

// --- Art Requests ---
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct ArtGrid {
/// The background for the cover art
pub background: ArtGridBackground,

/// The size of the cover art image
pub image_size: u64,

/// The dimension to use for this grid. A grid of dimension 3 has 3 images across and 3 images down, for a total of 9 images.
pub dimension: u64,

/// If cover art is missing for a given release_mbid, skip it and move on to the next one, if true is passed.
/// If false, the show-caa option will decide what happens.
#[serde(rename = "skip-missing")]
pub skip_missing: bool,

/// If cover art is missing and skip-missing is false,
/// then show-caa will determine if a blank square is shown or if the Cover Art Archive missing image is shown.
#[serde(rename = "show-caa")]
pub show_caa: bool,

/// The tiles parameter is a list of strings that determines the location where cover art images should be placed.
/// Each string is a comma separated list of image cells.
/// A grid of dimension 3 has 9 cells, from 0 in the upper left hand corner, 2 in the upper right hand corner,
/// 6 in the lower left corner and 8 in the lower right corner.
///
/// ```plaintext
/// 0 1 2
/// 3 4 5
/// 6 7 8
/// ```
///
/// Specifying only a single cell will have the image cover that cell exactly.
/// If more than one cell is specified, the image will cover the area defined by the bounding box of all the given cells.
/// These tiles only define bounding box areas – no clipping of images that may fall outside of these tiles will be performed.
///
/// ## Exemple
/// For this exemple, the dimension is set to 3
/// ```
/// let tiles = vec![
/// "0, 1, 3, 4".to_string(), // The first image cover the slots 0, 1, 3, and 4
/// "2".to_string(),
/// "5".to_string(),
/// "6".to_string(),
/// "7".to_string(),
/// "8".to_string(),
/// ];
/// ```
///
/// This will result in this arrangement:
///
/// ```plaintext
/// 1 1 2
/// 1 1 3
/// 4 5 6
/// ```
pub tiles: Option<Vec<String>>,

/// An ordered list of release_mbids. The images will be loaded and processed in the order that this list is in.
/// The cover art for the release_mbids will be placed on the tiles defined by the tiles parameter.
/// If release_group_mbids are supplied as well, ONLY cover arts for release_group_mbids will be processed.
pub release_mbids: Option<Vec<String>>,

/// An ordered list of release_group_mbids. The images will be loaded and processed in the order that this list is in.
/// The cover art for the release_group_mbids will be placed on the tiles defined by the tiles parameter.
/// If release_mbids are supplied as well, ONLY cover arts for release_mbids will be processed.
pub release_group_mbids: Option<Vec<String>>,

/// Size in pixels of each cover art in the composited image. Can be either 250 or 500
pub cover_art_size: Option<u64>,
}

/// The background for the cover art
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum ArtGridBackground {
#[serde(rename = "transparent")]
Transparent,
#[serde(rename = "white")]
White,
#[serde(rename = "black")]
Black,
}
27 changes: 24 additions & 3 deletions src/raw/response/art/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
//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
use serde::Deserialize;
use serde::Serialize;

// --------- POST /1/art/grid/
// https://listenbrainz.readthedocs.io/en/latest/users/api/art.html#post--1-art-grid-

/// Response type for [`Client::user_similar_users`](super::Client::user_similar_users).
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
pub struct ArtGridResponse {
pub rate_limit: Option<crate::raw::response::RateLimit>,
pub image: String,
}

// This response is special as it return a SVG directly. So we implement `ResponseType` manually
impl crate::raw::response::ResponseType for ArtGridResponse {
fn from_response(
response: crate::raw::response::Response,
) -> Result<Self, crate::raw::response::Error> {
let response = crate::raw::response::Error::try_from_error_response(response)?;
let rate_limit = crate::raw::response::RateLimit::from_headers(&response);
let image = response.text()?;
Ok(Self { image, rate_limit })
}
}
3 changes: 2 additions & 1 deletion src/raw/response/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use attohttpc::Response;
use serde::de::DeserializeOwned;
use serde::Deserialize;
use serde::Serialize;

use crate::Error;

Expand Down Expand Up @@ -45,7 +46,7 @@ pub use crate::raw::response::statistics::*;
/// 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)]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct RateLimit {
pub limit: u64,
pub remaining: u64,
Expand Down