Compare commits
5 commits
90faa09eff
...
41216c56a9
Author | SHA1 | Date | |
---|---|---|---|
41216c56a9 | |||
4cd73d8ae1 | |||
ca438da4de | |||
e9e3bdc26d | |||
692e112292 |
25 changed files with 2417 additions and 66 deletions
200
src/api_model.rs
200
src/api_model.rs
|
@ -1,6 +1,7 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{de::Visitor, Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::error::{Error, Result as MxmResult};
|
||||
|
||||
//#COMMON
|
||||
|
||||
|
@ -23,6 +24,7 @@ pub enum MessageBody<T> {
|
|||
EmptyArr(Vec<()>),
|
||||
// "body": {}
|
||||
EmptyObj {},
|
||||
EmptyStr(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -32,17 +34,17 @@ pub struct Header {
|
|||
#[serde(default)]
|
||||
pub hint: String,
|
||||
/// Is the requested track instrumental?
|
||||
#[serde(default)]
|
||||
pub instrumental: u8,
|
||||
#[serde(default, deserialize_with = "bool_from_int")]
|
||||
pub instrumental: bool,
|
||||
}
|
||||
|
||||
impl<T> Resp<T> {
|
||||
pub fn body_or_err(self) -> Result<T> {
|
||||
pub fn body_or_err(self) -> MxmResult<T> {
|
||||
match (self.message.body, self.message.header.status_code < 400) {
|
||||
(Some(MessageBody::Some(body)), true) => Ok(body),
|
||||
(_, true) => Err(Error::NoData),
|
||||
(_, false) => {
|
||||
if self.message.header.instrumental > 0 {
|
||||
if self.message.header.instrumental {
|
||||
Err(Error::Instrumental)
|
||||
} else if self.message.header.status_code == 404 {
|
||||
Err(Error::NotFound)
|
||||
|
@ -122,6 +124,145 @@ pub struct ErrorMxm {
|
|||
pub description: String,
|
||||
}
|
||||
|
||||
pub fn bool_from_int<'de, D>(deserializer: D) -> Result<bool, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct BoolFromIntVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for BoolFromIntVisitor {
|
||||
type Value = bool;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a number or boolean")
|
||||
}
|
||||
|
||||
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(v != 0)
|
||||
}
|
||||
|
||||
fn visit_u16<E>(self, v: u16) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(v != 0)
|
||||
}
|
||||
|
||||
fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(v != 0)
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(v != 0)
|
||||
}
|
||||
|
||||
fn visit_u128<E>(self, v: u128) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(v != 0)
|
||||
}
|
||||
|
||||
fn visit_i8<E>(self, v: i8) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(v != 0)
|
||||
}
|
||||
|
||||
fn visit_i16<E>(self, v: i16) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(v != 0)
|
||||
}
|
||||
|
||||
fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(v != 0)
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(v != 0)
|
||||
}
|
||||
|
||||
fn visit_i128<E>(self, v: i128) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(v != 0)
|
||||
}
|
||||
|
||||
fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(v != 0.0)
|
||||
}
|
||||
|
||||
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(v != 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(BoolFromIntVisitor)
|
||||
}
|
||||
|
||||
pub fn optional_date<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct OptionalDateVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for OptionalDateVisitor {
|
||||
type Value = Option<DateTime<Utc>>;
|
||||
|
||||
fn expecting(
|
||||
&self,
|
||||
formatter: &mut serde::__private::fmt::Formatter,
|
||||
) -> serde::__private::fmt::Result {
|
||||
formatter.write_str("timestamp or empty string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
if v.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
v.parse().map_err(E::custom).map(Some)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(OptionalDateVisitor)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -184,4 +325,51 @@ mod tests {
|
|||
"Error 403 returned by the Musixmatch API. Message: ''"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_instrumental() {
|
||||
let json = r#"{"message":{"header":{"status_code":404,"execute_time":0.002,"instrumental":true}}}"#;
|
||||
|
||||
let res = serde_json::from_str::<Resp<SubtitleBody>>(json).unwrap();
|
||||
|
||||
assert_eq!(res.message.header.status_code, 404);
|
||||
assert!(res.message.header.instrumental);
|
||||
assert!(res.message.body.is_none());
|
||||
|
||||
let err = res.body_or_err().unwrap_err();
|
||||
assert!(matches!(err, Error::Instrumental));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_instrumental2() {
|
||||
let json =
|
||||
r#"{"message":{"header":{"status_code":404,"execute_time":0.002,"instrumental":1}}}"#;
|
||||
|
||||
let res = serde_json::from_str::<Resp<SubtitleBody>>(json).unwrap();
|
||||
|
||||
assert_eq!(res.message.header.status_code, 404);
|
||||
assert!(res.message.header.instrumental);
|
||||
assert!(res.message.body.is_none());
|
||||
|
||||
let err = res.body_or_err().unwrap_err();
|
||||
assert!(matches!(err, Error::Instrumental));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_optional_date() {
|
||||
#[derive(Deserialize)]
|
||||
struct S {
|
||||
#[serde(deserialize_with = "optional_date")]
|
||||
date: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
let json_empty_string = r#"{"date": ""}"#;
|
||||
let json_date = r#"{"date": "2022-08-27T23:47:20Z"}"#;
|
||||
|
||||
let res = serde_json::from_str::<S>(json_empty_string).unwrap();
|
||||
assert!(res.date.is_none());
|
||||
|
||||
let res = serde_json::from_str::<S>(json_date).unwrap();
|
||||
assert!(res.date.is_some());
|
||||
}
|
||||
}
|
||||
|
|
101
src/apis/album_api.rs
Normal file
101
src/apis/album_api.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
use crate::error::Result;
|
||||
use crate::models::album::{Album, AlbumBody, AlbumListBody};
|
||||
use crate::models::{AlbumId, ArtistId, SortOrder};
|
||||
use crate::Musixmatch;
|
||||
|
||||
impl Musixmatch {
|
||||
/// Get the metadata for an album specified by its ID.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `id`: [Album ID](crate::models::AlbumId)
|
||||
///
|
||||
/// # Reference
|
||||
/// <https://developer.musixmatch.com/documentation/api-reference/album-get>
|
||||
pub async fn album(&self, id: AlbumId<'_>) -> Result<Album> {
|
||||
let mut url = self.new_url("album.get");
|
||||
{
|
||||
let mut url_query = url.query_pairs_mut();
|
||||
|
||||
let id_param = id.to_param();
|
||||
url_query.append_pair(id_param.0, &id_param.1);
|
||||
url_query.finish();
|
||||
}
|
||||
|
||||
let album_body = self.execute_get_request::<AlbumBody>(&url).await?;
|
||||
Ok(album_body.album)
|
||||
}
|
||||
|
||||
/// Get the album discography of an artist specified by its ID.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `id`: [Artist ID](crate::models::ArtistId)
|
||||
/// - `s_release_date`: Sort the albums by release date. If None, the albums are
|
||||
/// sorted by popularity
|
||||
/// - `page_size`: Define the page size for paginated results. Range is 1 to 100.
|
||||
/// - `page`: Define the page number for paginated results, starting from 1.
|
||||
///
|
||||
/// # Reference
|
||||
/// <https://developer.musixmatch.com/documentation/api-reference/artist-albums-get>
|
||||
pub async fn artist_albums(
|
||||
&self,
|
||||
artist_id: ArtistId<'_>,
|
||||
s_release_date: Option<SortOrder>,
|
||||
page_size: u8,
|
||||
page: u32,
|
||||
) -> Result<Vec<Album>> {
|
||||
let mut url = self.new_url("artist.albums.get");
|
||||
{
|
||||
let mut url_query = url.query_pairs_mut();
|
||||
|
||||
let id_param = artist_id.to_param();
|
||||
url_query.append_pair(id_param.0, &id_param.1);
|
||||
if let Some(s_release_date) = s_release_date {
|
||||
url_query.append_pair("s_release_date", s_release_date.as_str());
|
||||
}
|
||||
url_query.append_pair("page_size", &page_size.to_string());
|
||||
url_query.append_pair("page", &page.to_string());
|
||||
url_query.finish();
|
||||
}
|
||||
|
||||
let album_list_body = self.execute_get_request::<AlbumListBody>(&url).await?;
|
||||
Ok(album_list_body
|
||||
.album_list
|
||||
.into_iter()
|
||||
.map(|a| a.album)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// This api provides you the list of the top albums of a given country.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `country`: A valid country code (default: "US")
|
||||
/// - `page_size`: Define the page size for paginated results. Range is 1 to 100.
|
||||
/// - `page`: Define the page number for paginated results, starting from 1.
|
||||
///
|
||||
/// # Reference
|
||||
/// None - please update your documentation, Musixmatch!
|
||||
pub async fn chart_albums(
|
||||
&self,
|
||||
country: &str,
|
||||
page_size: u8,
|
||||
page: u32,
|
||||
) -> Result<Vec<Album>> {
|
||||
let mut url = self.new_url("chart.albums.get");
|
||||
{
|
||||
let mut url_query = url.query_pairs_mut();
|
||||
|
||||
url_query.append_pair("country", country);
|
||||
url_query.append_pair("chart_name", "new_releases");
|
||||
url_query.append_pair("page_size", &page_size.to_string());
|
||||
url_query.append_pair("page", &page.to_string());
|
||||
url_query.finish();
|
||||
}
|
||||
|
||||
let album_list_body = self.execute_get_request::<AlbumListBody>(&url).await?;
|
||||
Ok(album_list_body
|
||||
.album_list
|
||||
.into_iter()
|
||||
.map(|a| a.album)
|
||||
.collect())
|
||||
}
|
||||
}
|
129
src/apis/artist_api.rs
Normal file
129
src/apis/artist_api.rs
Normal file
|
@ -0,0 +1,129 @@
|
|||
use crate::error::Result;
|
||||
use crate::models::artist::{Artist, ArtistBody, ArtistListBody};
|
||||
use crate::models::ArtistId;
|
||||
use crate::Musixmatch;
|
||||
|
||||
impl Musixmatch {
|
||||
/// Get the metadata for an artist specified by its ID.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `id`: [Artist ID](crate::models::ArtistId)
|
||||
///
|
||||
/// # Reference
|
||||
/// <https://developer.musixmatch.com/documentation/api-reference/artist-get>
|
||||
pub async fn artist(&self, id: ArtistId<'_>) -> Result<Artist> {
|
||||
let mut url = self.new_url("artist.get");
|
||||
{
|
||||
let mut url_query = url.query_pairs_mut();
|
||||
|
||||
let id_param = id.to_param();
|
||||
url_query.append_pair(id_param.0, &id_param.1);
|
||||
url_query.finish();
|
||||
}
|
||||
|
||||
let artist_body = self.execute_get_request::<ArtistBody>(&url).await?;
|
||||
Ok(artist_body.artist)
|
||||
}
|
||||
|
||||
/// Get a list of artists somehow related to the one specified by its ID.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `id`: [Artist ID](crate::models::ArtistId)
|
||||
/// - `page_size`: Define the page size for paginated results. Range is 1 to 100.
|
||||
/// - `page`: Define the page number for paginated results, starting from 1.
|
||||
///
|
||||
/// # Reference
|
||||
/// <https://developer.musixmatch.com/documentation/api-reference/artist-related-get>
|
||||
pub async fn artist_related(
|
||||
&self,
|
||||
id: ArtistId<'_>,
|
||||
page_size: u8,
|
||||
page: u32,
|
||||
) -> Result<Vec<Artist>> {
|
||||
let mut url = self.new_url("artist.related.get");
|
||||
{
|
||||
let mut url_query = url.query_pairs_mut();
|
||||
|
||||
let id_param = id.to_param();
|
||||
url_query.append_pair(id_param.0, &id_param.1);
|
||||
url_query.append_pair("page_size", &page_size.to_string());
|
||||
url_query.append_pair("page", &page.to_string());
|
||||
url_query.finish();
|
||||
}
|
||||
|
||||
let artist_list_body = self.execute_get_request::<ArtistListBody>(&url).await?;
|
||||
Ok(artist_list_body
|
||||
.artist_list
|
||||
.into_iter()
|
||||
.map(|a| a.artist)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Search for artists in the Musixmatch database.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `q_artist`: Search term
|
||||
/// - `page_size`: Define the page size for paginated results. Range is 1 to 100.
|
||||
/// - `page`: Define the page number for paginated results, starting from 1.
|
||||
///
|
||||
/// # Reference
|
||||
/// <https://developer.musixmatch.com/documentation/api-reference/artist-search>
|
||||
pub async fn artist_search(
|
||||
&self,
|
||||
q_artist: &str,
|
||||
page_size: u8,
|
||||
page: u32,
|
||||
) -> Result<Vec<Artist>> {
|
||||
let mut url = self.new_url("artist.search");
|
||||
{
|
||||
let mut url_query = url.query_pairs_mut();
|
||||
|
||||
url_query.append_pair("q_artist", q_artist);
|
||||
url_query.append_pair("page_size", &page_size.to_string());
|
||||
url_query.append_pair("page", &page.to_string());
|
||||
url_query.finish();
|
||||
}
|
||||
|
||||
let artist_list_body = self.execute_get_request::<ArtistListBody>(&url).await?;
|
||||
Ok(artist_list_body
|
||||
.artist_list
|
||||
.into_iter()
|
||||
.map(|a| a.artist)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// This api provides you the list of the top artists of a given country.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `country`: A valid country code (default: "US")
|
||||
/// - `page_size`: Define the page size for paginated results. Range is 1 to 100.
|
||||
/// - `page`: Define the page number for paginated results, starting from 1.
|
||||
///
|
||||
/// # Reference
|
||||
/// <https://developer.musixmatch.com/documentation/api-reference/artist-chart-get>
|
||||
pub async fn chart_artists(
|
||||
&self,
|
||||
country: &str,
|
||||
page_size: u8,
|
||||
page: u32,
|
||||
) -> Result<Vec<Artist>> {
|
||||
let mut url = self.new_url("chart.artists.get");
|
||||
{
|
||||
let mut url_query = url.query_pairs_mut();
|
||||
|
||||
if !country.is_empty() {
|
||||
url_query.append_pair("country", country);
|
||||
}
|
||||
url_query.append_pair("page_size", &page_size.to_string());
|
||||
url_query.append_pair("page", &page.to_string());
|
||||
url_query.finish();
|
||||
}
|
||||
|
||||
let artist_list_body = self.execute_get_request::<ArtistListBody>(&url).await?;
|
||||
Ok(artist_list_body
|
||||
.artist_list
|
||||
.into_iter()
|
||||
.map(|a| a.artist)
|
||||
.collect())
|
||||
}
|
||||
}
|
51
src/apis/lyrics_api.rs
Normal file
51
src/apis/lyrics_api.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
use crate::error::Result;
|
||||
use crate::models::lyrics::{Lyrics, LyricsBody};
|
||||
use crate::models::TrackId;
|
||||
use crate::Musixmatch;
|
||||
|
||||
impl Musixmatch {
|
||||
/// Get the lyrics for a track specified by its name and artist.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `q_track`: Title of the track
|
||||
/// - `q_artist`: Artist of the track
|
||||
///
|
||||
/// # Reference
|
||||
/// <https://developer.musixmatch.com/documentation/api-reference/matcher-lyrics-get>
|
||||
pub async fn matcher_lyrics(&self, q_track: &str, q_artist: &str) -> Result<Lyrics> {
|
||||
let mut url = self.new_url("matcher.lyrics.get");
|
||||
{
|
||||
let mut url_query = url.query_pairs_mut();
|
||||
if !q_track.is_empty() {
|
||||
url_query.append_pair("q_track", q_track);
|
||||
}
|
||||
if !q_artist.is_empty() {
|
||||
url_query.append_pair("q_artist", q_artist);
|
||||
}
|
||||
url_query.finish();
|
||||
}
|
||||
|
||||
let lyrics_body = self.execute_get_request::<LyricsBody>(&url).await?;
|
||||
Ok(lyrics_body.lyrics)
|
||||
}
|
||||
|
||||
/// Get the lyrics for a track specified by its ID.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `id`: [Track ID](crate::models::TrackId)
|
||||
///
|
||||
/// # Reference
|
||||
/// <https://developer.musixmatch.com/documentation/api-reference/track-lyrics-get>
|
||||
pub async fn track_lyrics(&self, id: TrackId<'_>) -> Result<Lyrics> {
|
||||
let mut url = self.new_url("track.lyrics.get");
|
||||
{
|
||||
let mut url_query = url.query_pairs_mut();
|
||||
let id_param = id.to_param();
|
||||
url_query.append_pair(id_param.0, &id_param.1);
|
||||
url_query.finish();
|
||||
}
|
||||
|
||||
let lyrics_body = self.execute_get_request::<LyricsBody>(&url).await?;
|
||||
Ok(lyrics_body.lyrics)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
// pub mod album_api;
|
||||
// pub mod artist_api;
|
||||
// pub mod lyrics_api;
|
||||
// pub mod snippet_api;
|
||||
pub mod subtitle_api;
|
||||
// pub mod track_api;
|
||||
mod album_api;
|
||||
mod artist_api;
|
||||
mod lyrics_api;
|
||||
mod snippet_api;
|
||||
mod subtitle_api;
|
||||
mod track_api;
|
||||
|
|
20
src/apis/snippet_api.rs
Normal file
20
src/apis/snippet_api.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
use crate::error::Result;
|
||||
use crate::models::snippet::{Snippet, SnippetBody};
|
||||
use crate::models::TrackId;
|
||||
use crate::Musixmatch;
|
||||
|
||||
impl Musixmatch {
|
||||
pub async fn track_snippet(&self, id: TrackId<'_>) -> Result<Snippet> {
|
||||
let mut url = self.new_url("track.snippet.get");
|
||||
{
|
||||
let mut url_query = url.query_pairs_mut();
|
||||
|
||||
let id_param = id.to_param();
|
||||
url_query.append_pair(id_param.0, &id_param.1);
|
||||
url_query.finish();
|
||||
}
|
||||
|
||||
let snippet_body = self.execute_get_request::<SnippetBody>(&url).await?;
|
||||
Ok(snippet_body.snippet)
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ impl Musixmatch {
|
|||
/// so this should be the recommended value.
|
||||
///
|
||||
/// # Reference
|
||||
/// <https://developer.musixmatch.com/documentation/api-reference/track-subtitle-get>
|
||||
/// <https://developer.musixmatch.com/documentation/api-reference/matcher-subtitle-get>
|
||||
pub async fn matcher_subtitle(
|
||||
&self,
|
||||
q_track: &str,
|
||||
|
@ -28,8 +28,12 @@ impl Musixmatch {
|
|||
let mut url = self.new_url("matcher.subtitle.get");
|
||||
{
|
||||
let mut url_query = url.query_pairs_mut();
|
||||
if !q_track.is_empty() {
|
||||
url_query.append_pair("q_track", q_track);
|
||||
}
|
||||
if !q_artist.is_empty() {
|
||||
url_query.append_pair("q_artist", q_artist);
|
||||
}
|
||||
url_query.append_pair("subtitle_format", subtitle_format.to_param());
|
||||
if let Some(f_subtitle_length) = f_subtitle_length {
|
||||
url_query.append_pair("f_subtitle_length", &f_subtitle_length.to_string());
|
||||
|
@ -44,7 +48,6 @@ impl Musixmatch {
|
|||
}
|
||||
|
||||
let subtitle_body = self.execute_get_request::<SubtitleBody>(&url).await?;
|
||||
|
||||
Ok(subtitle_body.subtitle)
|
||||
}
|
||||
|
||||
|
@ -87,7 +90,6 @@ impl Musixmatch {
|
|||
}
|
||||
|
||||
let subtitle_body = self.execute_get_request::<SubtitleBody>(&url).await?;
|
||||
|
||||
Ok(subtitle_body.subtitle)
|
||||
}
|
||||
}
|
||||
|
|
426
src/apis/track_api.rs
Normal file
426
src/apis/track_api.rs
Normal file
|
@ -0,0 +1,426 @@
|
|||
use chrono::NaiveDate;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::models::track::{Track, TrackBody, TrackListBody};
|
||||
use crate::models::{AlbumId, ChartName, Genre, Genres, SortOrder, TrackId};
|
||||
use crate::Musixmatch;
|
||||
|
||||
impl Musixmatch {
|
||||
/// Get the metadata for a track specified by its name, artist and album.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `q_track`: Title of the track
|
||||
/// - `q_artist`: Artist of the track
|
||||
/// - `q_album`: Album name of the track
|
||||
/// - `translation_status`: Get the lyrics [translation status](crate::models::TrackLyricsTranslationStatus)
|
||||
/// - `lang_3c`: Output the lyrics translation status with
|
||||
/// [ISO 639‑2](https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes) 3-letter language codes
|
||||
/// instead of [ISO 639‑1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes.
|
||||
///
|
||||
/// # Reference
|
||||
/// <https://developer.musixmatch.com/documentation/api-reference/matcher-track-get>
|
||||
pub async fn matcher_track(
|
||||
&self,
|
||||
q_track: &str,
|
||||
q_artist: &str,
|
||||
q_album: &str,
|
||||
translation_status: bool,
|
||||
lang_3c: bool,
|
||||
) -> Result<Track> {
|
||||
let mut url = self.new_url("matcher.track.get");
|
||||
{
|
||||
let mut url_query = url.query_pairs_mut();
|
||||
|
||||
if !q_track.is_empty() {
|
||||
url_query.append_pair("q_track", q_track);
|
||||
}
|
||||
if !q_artist.is_empty() {
|
||||
url_query.append_pair("q_artist", q_artist);
|
||||
}
|
||||
if !q_album.is_empty() {
|
||||
url_query.append_pair("q_album", q_album);
|
||||
}
|
||||
if translation_status {
|
||||
url_query.append_pair("part", "track_lyrics_translation_status");
|
||||
url_query.append_pair(
|
||||
"language_iso_code",
|
||||
match lang_3c {
|
||||
true => "0",
|
||||
false => "1",
|
||||
},
|
||||
);
|
||||
}
|
||||
url_query.finish();
|
||||
}
|
||||
|
||||
let track_body = self.execute_get_request::<TrackBody>(&url).await?;
|
||||
Ok(track_body.track)
|
||||
}
|
||||
|
||||
/// Get the metadata for a track specified by its id.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `id`: [Track ID](crate::models::TrackId)
|
||||
/// - `translation_status`: Get the lyrics [translation status](crate::models::TrackLyricsTranslationStatus)
|
||||
/// - `lang_3c`: Output the lyrics translation status with
|
||||
/// [ISO 639‑2](https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes) 3-letter language codes
|
||||
/// instead of [ISO 639‑1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes.
|
||||
///
|
||||
/// # Reference
|
||||
/// <https://developer.musixmatch.com/documentation/api-reference/track-get>
|
||||
pub async fn track(
|
||||
&self,
|
||||
id: TrackId<'_>,
|
||||
translation_status: bool,
|
||||
lang_3c: bool,
|
||||
) -> Result<Track> {
|
||||
let mut url = self.new_url("track.get");
|
||||
{
|
||||
let mut url_query = url.query_pairs_mut();
|
||||
|
||||
let id_param = id.to_param();
|
||||
url_query.append_pair(id_param.0, &id_param.1);
|
||||
if translation_status {
|
||||
url_query.append_pair("part", "track_lyrics_translation_status");
|
||||
url_query.append_pair(
|
||||
"language_iso_code",
|
||||
match lang_3c {
|
||||
true => "0",
|
||||
false => "1",
|
||||
},
|
||||
);
|
||||
}
|
||||
url_query.finish();
|
||||
}
|
||||
|
||||
let track_body = self.execute_get_request::<TrackBody>(&url).await?;
|
||||
Ok(track_body.track)
|
||||
}
|
||||
|
||||
/// Get the list of songs of an album.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `id`: [Album ID](crate::models::AlbumId)
|
||||
/// - `f_has_lyrics`: When true, filter only contents with lyrics
|
||||
/// - `page_size`: Define the page size for paginated results. Range is 1 to 100.
|
||||
/// - `page`: Define the page number for paginated results, starting from 1.
|
||||
///
|
||||
/// # Reference
|
||||
/// <https://developer.musixmatch.com/documentation/api-reference/album-tracks-get>
|
||||
pub async fn album_tracks(
|
||||
&self,
|
||||
id: AlbumId<'_>,
|
||||
f_has_lyrics: bool,
|
||||
page_size: u8,
|
||||
page: u32,
|
||||
) -> Result<Vec<Track>> {
|
||||
let mut url = self.new_url("album.tracks.get");
|
||||
{
|
||||
let mut url_query = url.query_pairs_mut();
|
||||
|
||||
let id_param = id.to_param();
|
||||
url_query.append_pair(id_param.0, &id_param.1);
|
||||
if f_has_lyrics {
|
||||
url_query.append_pair("f_has_lyrics", "1");
|
||||
}
|
||||
url_query.append_pair("page_size", &page_size.to_string());
|
||||
url_query.append_pair("page", &page.to_string());
|
||||
url_query.finish();
|
||||
}
|
||||
|
||||
let track_list_body = self.execute_get_request::<TrackListBody>(&url).await?;
|
||||
Ok(track_list_body
|
||||
.track_list
|
||||
.into_iter()
|
||||
.map(|t| t.track)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Get a list of the top songs of a given country.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `country`: A valid country code (default: "US")
|
||||
/// - `chart_name`: Select among [available charts](crate::models::ChartName)
|
||||
/// - `f_has_lyrics`: When true, filter only contents with lyrics
|
||||
/// - `page_size`: Define the page size for paginated results. Range is 1 to 100.
|
||||
/// - `page`: Define the page number for paginated results, starting from 1.
|
||||
///
|
||||
/// # Reference
|
||||
/// <https://developer.musixmatch.com/documentation/api-reference/track-chart-get>
|
||||
pub async fn chart_tracks(
|
||||
&self,
|
||||
country: &str,
|
||||
chart_name: ChartName,
|
||||
f_has_lyrics: bool,
|
||||
page_size: u8,
|
||||
page: u32,
|
||||
) -> Result<Vec<Track>> {
|
||||
let mut url = self.new_url("chart.tracks.get");
|
||||
{
|
||||
let mut url_query = url.query_pairs_mut();
|
||||
|
||||
if !country.is_empty() {
|
||||
url_query.append_pair("country", country);
|
||||
}
|
||||
url_query.append_pair("chart_name", chart_name.as_str());
|
||||
if f_has_lyrics {
|
||||
url_query.append_pair("f_has_lyrics", "1");
|
||||
}
|
||||
url_query.append_pair("page_size", &page_size.to_string());
|
||||
url_query.append_pair("page", &page.to_string());
|
||||
url_query.finish();
|
||||
}
|
||||
|
||||
let track_list_body = self.execute_get_request::<TrackListBody>(&url).await?;
|
||||
Ok(track_list_body
|
||||
.track_list
|
||||
.into_iter()
|
||||
.map(|t| t.track)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Get the list of the music genres the Musixmatch catalogue.
|
||||
///
|
||||
/// # Reference
|
||||
/// <https://developer.musixmatch.com/documentation/api-reference/music-genres-get>
|
||||
pub async fn genres(&self) -> Result<Vec<Genre>> {
|
||||
let url = self.new_url("music.genres.get");
|
||||
let genres = self.execute_get_request::<Genres>(&url).await?;
|
||||
Ok(genres.music_genre_list)
|
||||
}
|
||||
|
||||
/// Create a new query builder for searching tracks in the Musixmatch database.
|
||||
///
|
||||
/// **Note:** The search results are unsorted the by default. You probably want
|
||||
/// to sort by popularity (`.s_track_rating(SortOrder::Desc)`) to get relevant results.
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// let tracks = musixmatch
|
||||
/// .track_search()
|
||||
/// .q_lyrics("Never gonna run around and desert you")
|
||||
/// .s_track_rating(SortOrder::Desc)
|
||||
/// .send(10, 1)
|
||||
/// .await?;
|
||||
/// ```
|
||||
///
|
||||
/// # Reference
|
||||
/// <https://developer.musixmatch.com/documentation/api-reference/track-search>
|
||||
pub fn track_search(&self) -> TrackSearchQuery {
|
||||
TrackSearchQuery {
|
||||
mxm: self.clone(),
|
||||
q_track: None,
|
||||
q_artist: None,
|
||||
q_lyrics: None,
|
||||
q_track_artist: None,
|
||||
q_writer: None,
|
||||
q: None,
|
||||
f_artist_id: None,
|
||||
f_music_genre_id: None,
|
||||
f_lyrics_language: None,
|
||||
f_has_lyrics: false,
|
||||
f_track_release_group_first_release_date_min: None,
|
||||
f_track_release_group_first_release_date_max: None,
|
||||
s_artist_rating: None,
|
||||
s_track_rating: None,
|
||||
quorum_factor: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TrackSearchQuery<'a> {
|
||||
mxm: Musixmatch,
|
||||
|
||||
q_track: Option<&'a str>,
|
||||
q_artist: Option<&'a str>,
|
||||
q_lyrics: Option<&'a str>,
|
||||
q_track_artist: Option<&'a str>,
|
||||
q_writer: Option<&'a str>,
|
||||
q: Option<&'a str>,
|
||||
f_artist_id: Option<u64>,
|
||||
f_music_genre_id: Option<u64>,
|
||||
f_lyrics_language: Option<&'a str>,
|
||||
f_has_lyrics: bool,
|
||||
f_track_release_group_first_release_date_min: Option<NaiveDate>,
|
||||
f_track_release_group_first_release_date_max: Option<NaiveDate>,
|
||||
s_artist_rating: Option<SortOrder>,
|
||||
s_track_rating: Option<SortOrder>,
|
||||
quorum_factor: Option<f32>,
|
||||
}
|
||||
|
||||
impl<'a> TrackSearchQuery<'a> {
|
||||
/// Track title
|
||||
pub fn q_track(mut self, q_track: &'a str) -> Self {
|
||||
self.q_track = Some(q_track);
|
||||
self
|
||||
}
|
||||
|
||||
/// Track artist
|
||||
pub fn q_artist(mut self, q_artist: &'a str) -> Self {
|
||||
self.q_artist = Some(q_artist);
|
||||
self
|
||||
}
|
||||
|
||||
/// Any word in the lyrics
|
||||
pub fn q_lyrics(mut self, q_lyrics: &'a str) -> Self {
|
||||
self.q_lyrics = Some(q_lyrics);
|
||||
self
|
||||
}
|
||||
|
||||
/// Any word in the song title or artist name
|
||||
pub fn q_track_artist(mut self, q_track_artist: &'a str) -> Self {
|
||||
self.q_track_artist = Some(q_track_artist);
|
||||
self
|
||||
}
|
||||
|
||||
/// Track lyrics writer
|
||||
pub fn q_writer(mut self, q_writer: &'a str) -> Self {
|
||||
self.q_writer = Some(q_writer);
|
||||
self
|
||||
}
|
||||
|
||||
/// Any word in the song title or artist name or lyrics
|
||||
pub fn q(mut self, q: &'a str) -> Self {
|
||||
self.q = Some(q);
|
||||
self
|
||||
}
|
||||
|
||||
/// When set, filter by this artist ID
|
||||
pub fn f_artist_id(mut self, f_artist_id: u64) -> Self {
|
||||
self.f_artist_id = Some(f_artist_id);
|
||||
self
|
||||
}
|
||||
|
||||
/// When set, filter by this genre ID
|
||||
pub fn f_music_genre_id(mut self, f_music_genre_id: u64) -> Self {
|
||||
self.f_music_genre_id = Some(f_music_genre_id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Filter by the lyrics language ("en", "it", ...)
|
||||
pub fn f_lyrics_language(mut self, f_lyrics_language: &'a str) -> Self {
|
||||
self.f_lyrics_language = Some(f_lyrics_language);
|
||||
self
|
||||
}
|
||||
|
||||
/// When set, filter only contents with lyrics
|
||||
pub fn f_has_lyrics(mut self) -> Self {
|
||||
self.f_has_lyrics = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// When set, filter the tracks with release date newer than the given value
|
||||
pub fn f_track_release_date_min(mut self, f_track_release_date_min: NaiveDate) -> Self {
|
||||
self.f_track_release_group_first_release_date_min = Some(f_track_release_date_min);
|
||||
self
|
||||
}
|
||||
|
||||
/// When set, filter the tracks with release date older than the given value
|
||||
pub fn f_track_release_date_max(mut self, f_track_release_date_max: NaiveDate) -> Self {
|
||||
self.f_track_release_group_first_release_date_max = Some(f_track_release_date_max);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sort by artist popularity (asc|desc)
|
||||
pub fn s_artist_rating(mut self, s_artist_rating: SortOrder) -> Self {
|
||||
self.s_artist_rating = Some(s_artist_rating);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sort by track popularity (asc|desc)
|
||||
pub fn s_track_rating(mut self, s_track_rating: SortOrder) -> Self {
|
||||
self.s_track_rating = Some(s_track_rating);
|
||||
self
|
||||
}
|
||||
|
||||
/// Search only a part of the given query string.
|
||||
///
|
||||
/// Allowed range is (0.1 – 0.9)
|
||||
pub fn quorum_factor(mut self, quorum_factor: f32) -> Self {
|
||||
self.quorum_factor = Some(quorum_factor);
|
||||
self
|
||||
}
|
||||
|
||||
/// Execute the search query.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `page_size`: Define the page size for paginated results. Range is 1 to 100.
|
||||
/// - `page`: Define the page number for paginated results, starting from 1.
|
||||
pub async fn send(&self, page_size: u8, page: u32) -> Result<Vec<Track>> {
|
||||
let mut url = self.mxm.new_url("track.search");
|
||||
{
|
||||
let mut url_query = url.query_pairs_mut();
|
||||
|
||||
if let Some(q_track) = self.q_track {
|
||||
url_query.append_pair("q_track", q_track);
|
||||
}
|
||||
if let Some(q_artist) = self.q_artist {
|
||||
url_query.append_pair("q_artist", q_artist);
|
||||
}
|
||||
if let Some(q_lyrics) = self.q_lyrics {
|
||||
url_query.append_pair("q_lyrics", q_lyrics);
|
||||
}
|
||||
if let Some(q_track_artist) = self.q_track_artist {
|
||||
url_query.append_pair("q_track_artist", q_track_artist);
|
||||
}
|
||||
if let Some(q_writer) = self.q_writer {
|
||||
url_query.append_pair("q_writer", q_writer);
|
||||
}
|
||||
if let Some(q) = self.q {
|
||||
url_query.append_pair("q", q);
|
||||
}
|
||||
if let Some(f_artist_id) = self.f_artist_id {
|
||||
url_query.append_pair("f_artist_id", &f_artist_id.to_string());
|
||||
}
|
||||
if let Some(f_music_genre_id) = self.f_music_genre_id {
|
||||
url_query.append_pair("f_music_genre_id", &f_music_genre_id.to_string());
|
||||
}
|
||||
if let Some(f_lyrics_language) = self.f_lyrics_language {
|
||||
url_query.append_pair("f_lyrics_language", f_lyrics_language);
|
||||
}
|
||||
if self.f_has_lyrics {
|
||||
url_query.append_pair("f_has_lyrics", "1");
|
||||
}
|
||||
if let Some(f_track_release_group_first_release_date_min) =
|
||||
self.f_track_release_group_first_release_date_min
|
||||
{
|
||||
url_query.append_pair(
|
||||
"f_track_release_group_first_release_date_min",
|
||||
&f_track_release_group_first_release_date_min
|
||||
.format("%Y%m%d")
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
if let Some(f_track_release_group_first_release_date_max) =
|
||||
self.f_track_release_group_first_release_date_max
|
||||
{
|
||||
url_query.append_pair(
|
||||
"f_track_release_group_first_release_date_max",
|
||||
&f_track_release_group_first_release_date_max
|
||||
.format("%Y%m%d")
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
if let Some(s_artist_rating) = &self.s_artist_rating {
|
||||
url_query.append_pair("s_artist_rating", s_artist_rating.as_str());
|
||||
}
|
||||
if let Some(s_track_rating) = &self.s_track_rating {
|
||||
url_query.append_pair("s_track_rating", s_track_rating.as_str());
|
||||
}
|
||||
if let Some(quorum_factor) = self.quorum_factor {
|
||||
url_query.append_pair("quorum_factor", &quorum_factor.to_string());
|
||||
}
|
||||
|
||||
url_query.append_pair("page_size", &page_size.to_string());
|
||||
url_query.append_pair("page", &page.to_string());
|
||||
url_query.finish();
|
||||
}
|
||||
|
||||
let track_list_body = self.mxm.execute_get_request::<TrackListBody>(&url).await?;
|
||||
Ok(track_list_body
|
||||
.track_list
|
||||
.into_iter()
|
||||
.map(|t| t.track)
|
||||
.collect())
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ pub enum Error {
|
|||
TokenExpired,
|
||||
#[error("Error {status_code} returned by the Musixmatch API. Message: '{msg}'")]
|
||||
MusixmatchError { status_code: u16, msg: String },
|
||||
#[error("Musixmatch returned no data")]
|
||||
#[error("Musixmatch returned no data or data that could not be deserialized")]
|
||||
NoData,
|
||||
#[error("You entered wrong credentials")]
|
||||
WrongCredentials,
|
||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -33,6 +33,9 @@ mod error;
|
|||
pub mod models;
|
||||
pub mod storage;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use error::Error;
|
||||
|
||||
use chrono::{Datelike, Local};
|
||||
|
@ -49,8 +52,9 @@ use tokio::sync::Mutex;
|
|||
use crate::api_model::Resp;
|
||||
use crate::error::Result;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Musixmatch {
|
||||
inner: MusixmatchRef,
|
||||
inner: Arc<MusixmatchRef>,
|
||||
}
|
||||
|
||||
struct MusixmatchRef {
|
||||
|
@ -113,7 +117,7 @@ impl Musixmatch {
|
|||
.unwrap();
|
||||
|
||||
Self {
|
||||
inner: MusixmatchRef {
|
||||
inner: Arc::new(MusixmatchRef {
|
||||
http,
|
||||
storage,
|
||||
email: email.to_owned(),
|
||||
|
@ -122,7 +126,7 @@ impl Musixmatch {
|
|||
brand,
|
||||
device,
|
||||
ua,
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
86
src/models/album.rs
Normal file
86
src/models/album.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::Genres;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct AlbumBody {
|
||||
pub album: Album,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct AlbumListBody {
|
||||
pub album_list: Vec<AlbumBody>,
|
||||
}
|
||||
|
||||
/// Album: an album of songs in the Musixmatch database.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
pub struct Album {
|
||||
/// Unique Musixmatch Album ID
|
||||
pub album_id: u64,
|
||||
/// Musicbrainz album ID
|
||||
///
|
||||
/// **Note:** most albums dont have this entry set
|
||||
#[serde(default)]
|
||||
pub album_mbid: String,
|
||||
/// Album name
|
||||
pub album_name: String,
|
||||
/// Popularity of the album from 0 to 100
|
||||
#[serde(default)]
|
||||
pub album_rating: u8,
|
||||
/// Number of tracks on the album
|
||||
pub album_track_count: u16,
|
||||
/// Album release date (e.g. "2009-07-07")
|
||||
#[serde(default)]
|
||||
pub album_release_date: String,
|
||||
/// Album type (Single / EP / Album)
|
||||
pub album_release_type: String,
|
||||
|
||||
/// Musixmatch artist ID
|
||||
#[serde(default)]
|
||||
pub artist_id: u64,
|
||||
/// Artist name
|
||||
pub artist_name: String,
|
||||
|
||||
/// List of primary genres
|
||||
#[serde(default)]
|
||||
pub primary_genres: Genres,
|
||||
/// Secondary genres / Subgenres
|
||||
///
|
||||
/// Example: primary_genres: Pop, secondary_genres: K-Pop / Mandopop
|
||||
///
|
||||
/// Note that this schema is not applied to all tracks on Musixmatch.
|
||||
/// There are for example many K-Pop tracks with both Pop and K-Pop
|
||||
/// tagged as primary genre and this field empty.
|
||||
#[serde(default)]
|
||||
pub secondary_genres: Genres,
|
||||
|
||||
/// Album copyright text
|
||||
#[serde(default)]
|
||||
pub album_copyright: String,
|
||||
/// Album label / recording company
|
||||
#[serde(default)]
|
||||
pub album_label: String,
|
||||
/// Human-readable URL-safe Album ID
|
||||
///
|
||||
/// Example: `LMFAO/Party-Rock-5`
|
||||
pub album_vanity_id: String,
|
||||
|
||||
/// Date and time when the album was last updated
|
||||
pub updated_time: DateTime<Utc>,
|
||||
|
||||
/// Album cover URL (100x100px)
|
||||
#[serde(default)]
|
||||
pub album_coverart_100x100: String,
|
||||
/// Album cover URL (350x350px)
|
||||
#[serde(default)]
|
||||
pub album_coverart_350x350: String,
|
||||
/// Album cover URL (500x500px)
|
||||
#[serde(default)]
|
||||
pub album_coverart_500x500: String,
|
||||
/// Album cover URL (800x800px)
|
||||
///
|
||||
/// **Note:** not present on a lot of albums
|
||||
#[serde(default)]
|
||||
pub album_coverart_800x800: String,
|
||||
}
|
111
src/models/artist.rs
Normal file
111
src/models/artist.rs
Normal file
|
@ -0,0 +1,111 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::Genres;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct ArtistBody {
|
||||
pub artist: Artist,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct ArtistListBody {
|
||||
pub artist_list: Vec<ArtistBody>,
|
||||
}
|
||||
|
||||
/// Artist: an artist in the Musixmatch database.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
pub struct Artist {
|
||||
/// Musixmatch Artist ID
|
||||
pub artist_id: u64,
|
||||
/// Musicbrainz Artist ID
|
||||
///
|
||||
/// **Note:** most tracks dont have this entry set
|
||||
#[serde(default)]
|
||||
pub artist_mbid: String,
|
||||
|
||||
/// Artist name
|
||||
pub artist_name: String,
|
||||
/// Artist name in different languages
|
||||
#[serde(default)]
|
||||
pub artist_name_translation_list: Vec<ArtistNameTranslation>,
|
||||
/// Artist origin as a 2-letter country code (e.g. "US")
|
||||
#[serde(default)]
|
||||
pub artist_country: String,
|
||||
// Alternative names for the artist (e.g. in different languages)
|
||||
#[serde(default)]
|
||||
pub artist_alias_list: Vec<ArtistAlias>,
|
||||
/// Popularity of the artist from 0 to 100
|
||||
#[serde(default)]
|
||||
pub artist_rating: u8,
|
||||
|
||||
/// Primary genres of the artist
|
||||
#[serde(default)]
|
||||
pub primary_genres: Genres,
|
||||
/// Secondary genres / Subgenres
|
||||
///
|
||||
/// Example: primary_genres: Pop, secondary_genres: K-Pop / Mandopop
|
||||
///
|
||||
/// Note that this schema is not applied to all tracks on Musixmatch.
|
||||
/// There are for example many K-Pop tracks with both Pop and K-Pop
|
||||
/// tagged as primary genre and this field empty.
|
||||
#[serde(default)]
|
||||
pub secondary_genres: Genres,
|
||||
|
||||
#[serde(default)]
|
||||
pub artist_twitter_url: String,
|
||||
#[serde(default)]
|
||||
pub artist_website_url: String,
|
||||
#[serde(default)]
|
||||
pub artist_instagram_url: String,
|
||||
#[serde(default)]
|
||||
pub artist_tiktok_url: String,
|
||||
#[serde(default)]
|
||||
pub artist_facebook_url: String,
|
||||
|
||||
/// URL-safe human-readable artist ID
|
||||
///
|
||||
/// Example: "aespa"
|
||||
pub artist_vanity_id: String,
|
||||
/// Date and time when the artist was last updated
|
||||
pub updated_time: DateTime<Utc>,
|
||||
/// Year of the start of the artist's presence
|
||||
#[serde(default)]
|
||||
pub begin_date_year: String,
|
||||
/// Start date of the artist's presence in YYYY-MM-DD format
|
||||
///
|
||||
/// **Info:** the default value is `"0000-00-00"`
|
||||
#[serde(default)]
|
||||
pub begin_date: String,
|
||||
/// Year of the end of the artist's presence
|
||||
#[serde(default)]
|
||||
pub end_date_year: String,
|
||||
/// End date of the artist's presence in YYYY-MM-DD format
|
||||
///
|
||||
/// **Info:** the default value is `"0000-00-00"`
|
||||
#[serde(default)]
|
||||
pub end_date: String,
|
||||
}
|
||||
|
||||
/// Alternative artist name (e.g. different languages)
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
pub struct ArtistAlias {
|
||||
pub artist_alias: String,
|
||||
}
|
||||
|
||||
/// Alternative artist name (e.g. different languages)
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
pub struct ArtistNameTranslation {
|
||||
pub artist_name_translation: ArtistNameTranslationInner,
|
||||
}
|
||||
|
||||
/// Alternative artist name (e.g. different languages)
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
pub struct ArtistNameTranslationInner {
|
||||
/// Language code (e.g. "EN")
|
||||
///
|
||||
/// **Note:** the language code is uppercase for some reason
|
||||
pub language: String,
|
||||
/// Translated name
|
||||
pub translation: String,
|
||||
}
|
30
src/models/genre.rs
Normal file
30
src/models/genre.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub struct Genres {
|
||||
pub music_genre_list: Vec<Genre>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub struct Genre {
|
||||
pub music_genre: GenreInner,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub struct GenreInner {
|
||||
/// Unique Musixmatch Genre ID
|
||||
pub music_genre_id: u64,
|
||||
/// ID of supergenre
|
||||
pub music_genre_parent_id: u64,
|
||||
/// Genre name
|
||||
pub music_genre_name: String,
|
||||
/// Long genre name
|
||||
pub music_genre_name_extended: String,
|
||||
/// URL-safe genre name
|
||||
///
|
||||
/// **Note:** Jazz / Bebop (ID: 1291) has this set to None for some reason.
|
||||
pub music_genre_vanity: Option<String>,
|
||||
}
|
79
src/models/id.rs
Normal file
79
src/models/id.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
/// Track identifiers from different sources
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
pub enum TrackId<'a> {
|
||||
/// Musixmatch ID that is the same for equivalent tracks (e.g. same track on different albums)
|
||||
Commontrack(u64),
|
||||
/// Unique Musixmatch track ID
|
||||
TrackId(u64),
|
||||
/// Human-readable Musixmatch ID
|
||||
///
|
||||
/// Used in the URLs on the Musixmatch website.
|
||||
///
|
||||
/// Example: `aespa/Black-Mamba`
|
||||
CommontrackVanity(&'a str),
|
||||
/// [International Standard Recording Code](https://en.wikipedia.org/wiki/International_Standard_Recording_Code)
|
||||
Isrc(&'a str),
|
||||
/// Musicbrainz ID
|
||||
///
|
||||
/// **Note:** Musicbrainz IDs are not present for most tracks in
|
||||
/// the Musixmatch database, so use them only with a fallback.
|
||||
Musicbrainz(&'a str),
|
||||
/// Spotify track ID
|
||||
Spotify(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> TrackId<'a> {
|
||||
pub(crate) fn to_param(&self) -> (&'static str, String) {
|
||||
match self {
|
||||
TrackId::Commontrack(id) => ("commontrack_id", id.to_string()),
|
||||
TrackId::TrackId(id) => ("track_id", id.to_string()),
|
||||
TrackId::CommontrackVanity(id) => ("commontrack_vanity_id", id.to_string()),
|
||||
TrackId::Isrc(id) => ("track_isrc", id.to_string()),
|
||||
TrackId::Musicbrainz(id) => ("track_mbid", id.to_string()),
|
||||
TrackId::Spotify(id) => ("track_spotify_id", id.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Artist identifiers from different sources
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ArtistId<'a> {
|
||||
/// Unique Musixmatch artist ID
|
||||
ArtistId(u64),
|
||||
/// Musicbrainz ID
|
||||
///
|
||||
/// **Note:** Musicbrainz IDs are not present for most artists in
|
||||
/// the Musixmatch database, so use them only with a fallback.
|
||||
Musicbrainz(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> ArtistId<'a> {
|
||||
pub(crate) fn to_param(&self) -> (&'static str, String) {
|
||||
match self {
|
||||
ArtistId::ArtistId(id) => ("artist_id", id.to_string()),
|
||||
ArtistId::Musicbrainz(id) => ("artist_mbid", id.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Album identifiers from different sources
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AlbumId<'a> {
|
||||
/// Unique Musixmatch artist ID
|
||||
AlbumId(u64),
|
||||
/// Musicbrainz ID
|
||||
///
|
||||
/// **Note:** Musicbrainz IDs are not present for most artists in
|
||||
/// the Musixmatch database, so use them only with a fallback.
|
||||
Musicbrainz(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> AlbumId<'a> {
|
||||
pub(crate) fn to_param(&self) -> (&'static str, String) {
|
||||
match self {
|
||||
AlbumId::AlbumId(id) => ("album_id", id.to_string()),
|
||||
AlbumId::Musicbrainz(id) => ("album_mbid", id.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
42
src/models/lyrics.rs
Normal file
42
src/models/lyrics.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct LyricsBody {
|
||||
pub lyrics: Lyrics,
|
||||
}
|
||||
|
||||
/// Lyrics from the Musixmatch database.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub struct Lyrics {
|
||||
/// Unique Musixmatch lyrics ID
|
||||
pub lyrics_id: u64,
|
||||
/// True if the track is instrumental
|
||||
#[serde(default, deserialize_with = "crate::api_model::bool_from_int")]
|
||||
pub instrumental: bool,
|
||||
/// True if the lyrics contain explicit language
|
||||
#[serde(default, deserialize_with = "crate::api_model::bool_from_int")]
|
||||
pub explicit: bool,
|
||||
/// Lyrics text
|
||||
pub lyrics_body: String,
|
||||
/// Language code (e.g. "en")
|
||||
#[serde(default)]
|
||||
pub lyrics_language: String,
|
||||
/// Language name (e.g. "English")
|
||||
#[serde(default)]
|
||||
pub lyrics_language_description: String,
|
||||
/// Copyright text of the lyrics
|
||||
///
|
||||
/// Ends with a newline.
|
||||
///
|
||||
/// Example:
|
||||
/// ```text
|
||||
/// Writer(s): David Hodges
|
||||
/// Copyright: Emi Blackwood Music Inc., 12.06 Publishing, Hipgnosis Sfh I Limited, Hifi Music Ip Issuer L.p.
|
||||
/// ```
|
||||
#[serde(default)]
|
||||
pub lyrics_copyright: String,
|
||||
/// Date and time when the lyrics were last updated
|
||||
pub updated_time: DateTime<Utc>,
|
||||
}
|
|
@ -4,5 +4,33 @@ pub use subtitle::SubtitleFormat;
|
|||
pub use subtitle::SubtitleLine;
|
||||
pub use subtitle::SubtitleTime;
|
||||
|
||||
mod track_id;
|
||||
pub use track_id::TrackId;
|
||||
mod id;
|
||||
pub use id::AlbumId;
|
||||
pub use id::ArtistId;
|
||||
pub use id::TrackId;
|
||||
|
||||
pub(crate) mod lyrics;
|
||||
pub use lyrics::Lyrics;
|
||||
|
||||
pub(crate) mod track;
|
||||
pub use track::ChartName;
|
||||
pub use track::SortOrder;
|
||||
pub use track::Track;
|
||||
pub use track::TrackLyricsTranslationStatus;
|
||||
|
||||
mod genre;
|
||||
pub use genre::Genre;
|
||||
pub use genre::GenreInner;
|
||||
pub use genre::Genres;
|
||||
|
||||
pub(crate) mod artist;
|
||||
pub use artist::Artist;
|
||||
pub use artist::ArtistAlias;
|
||||
pub use artist::ArtistNameTranslation;
|
||||
pub use artist::ArtistNameTranslationInner;
|
||||
|
||||
pub(crate) mod album;
|
||||
pub use album::Album;
|
||||
|
||||
pub(crate) mod snippet;
|
||||
pub use snippet::Snippet;
|
||||
|
|
30
src/models/snippet.rs
Normal file
30
src/models/snippet.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct SnippetBody {
|
||||
pub snippet: Snippet,
|
||||
}
|
||||
|
||||
/// Snippet of lyrics text in the Musixmatch database.
|
||||
///
|
||||
/// A lyrics snippet is a very short representation of a song lyrics.
|
||||
/// It’s usually twenty to a hundred characters long and it’s calculated
|
||||
/// extracting a sequence of words from the lyrics.
|
||||
///
|
||||
/// Example: "There's not a thing that I would change"
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
pub struct Snippet {
|
||||
/// Unique Musixmatch Snippet ID
|
||||
pub snippet_id: u64,
|
||||
/// Snippet language code (e.g. "en")
|
||||
#[serde(default)]
|
||||
pub snippet_language: String,
|
||||
/// True if the track is instrumental
|
||||
#[serde(default, deserialize_with = "crate::api_model::bool_from_int")]
|
||||
pub instrumental: bool,
|
||||
/// Date and time when the snippet was last updated
|
||||
pub updated_time: DateTime<Utc>,
|
||||
/// Snippet text
|
||||
pub snippet_body: String,
|
||||
}
|
|
@ -158,9 +158,11 @@ pub struct Subtitle {
|
|||
/// Subtitle / synchronized lyrics in the requested format
|
||||
pub subtitle_body: String,
|
||||
/// Language code (e.g. "en")
|
||||
pub subtitle_language: Option<String>,
|
||||
#[serde(default)]
|
||||
pub subtitle_language: String,
|
||||
/// Language name (e.g. "English")
|
||||
pub subtitle_language_description: Option<String>,
|
||||
#[serde(default)]
|
||||
pub subtitle_language_description: String,
|
||||
/// Copyright text of the lyrics
|
||||
///
|
||||
/// Ends with a newline.
|
||||
|
|
194
src/models/track.rs
Normal file
194
src/models/track.rs
Normal file
|
@ -0,0 +1,194 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::Genres;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct TrackBody {
|
||||
pub track: Track,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct TrackListBody {
|
||||
pub track_list: Vec<TrackBody>,
|
||||
}
|
||||
|
||||
/// Track: a song in the Musixmatch database
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub struct Track {
|
||||
/// Unique Musixmatch Track ID
|
||||
pub track_id: u64,
|
||||
/// Track Musicbrainz ID
|
||||
///
|
||||
/// **Note:** most tracks dont have this entry set
|
||||
#[serde(default)]
|
||||
pub track_mbid: String,
|
||||
/// [International Standard Recording Code](https://en.wikipedia.org/wiki/International_Standard_Recording_Code)
|
||||
#[serde(default)]
|
||||
pub track_isrc: String,
|
||||
/// [ISRCs](https://en.wikipedia.org/wiki/International_Standard_Recording_Code) of equivalent tracks (e.g. on different albums)
|
||||
#[serde(default)]
|
||||
pub commontrack_isrcs: Vec<Vec<String>>,
|
||||
/// Track ID on Spotify
|
||||
#[serde(default)]
|
||||
pub track_spotify_id: String,
|
||||
/// Spotify IDs of equivalent tracks (e.g. on different albums)
|
||||
#[serde(default)]
|
||||
pub commontrack_spotify_ids: Vec<String>,
|
||||
/// Track ID on Soundcloud
|
||||
#[serde(default)]
|
||||
pub track_soundcloud_id: u64,
|
||||
/// Track ID on XBox Music
|
||||
#[serde(default)]
|
||||
pub track_xboxmusic_id: String,
|
||||
|
||||
/// Title of the track
|
||||
pub track_name: String,
|
||||
/// Popularity of the track from 0 to 100
|
||||
#[serde(default)]
|
||||
pub track_rating: u8,
|
||||
/// Length of the track in seconds
|
||||
#[serde(default)]
|
||||
pub track_length: u32,
|
||||
/// Musixmatch ID that is the same for equivalent tracks (e.g. same track on different albums)
|
||||
pub commontrack_id: u64,
|
||||
|
||||
/// True if the track is instrumental
|
||||
#[serde(default, deserialize_with = "crate::api_model::bool_from_int")]
|
||||
pub instrumental: bool,
|
||||
/// True if the lyrics contain explicit language
|
||||
#[serde(default, deserialize_with = "crate::api_model::bool_from_int")]
|
||||
pub explicit: bool,
|
||||
/// True if lyrics are available
|
||||
#[serde(default, deserialize_with = "crate::api_model::bool_from_int")]
|
||||
pub has_lyrics: bool,
|
||||
/// True if subtitles (synchronized lyrics) are available
|
||||
#[serde(default, deserialize_with = "crate::api_model::bool_from_int")]
|
||||
pub has_subtitles: bool,
|
||||
/// True if richsync lyrics (synchronized by word) are available
|
||||
#[serde(default, deserialize_with = "crate::api_model::bool_from_int")]
|
||||
pub has_richsync: bool,
|
||||
/// True if the track structure is available
|
||||
#[serde(default, deserialize_with = "crate::api_model::bool_from_int")]
|
||||
pub has_track_structure: bool,
|
||||
|
||||
/// Amount of users that favorited the track on Musixmatch
|
||||
#[serde(default)]
|
||||
pub num_favourite: u32,
|
||||
|
||||
/// Musixmatch lyrics ID
|
||||
#[serde(default)]
|
||||
pub lyrics_id: u64,
|
||||
/// Musixmatch subtitle ID
|
||||
#[serde(default)]
|
||||
pub subtitle_id: u64,
|
||||
|
||||
/// Musixmatch album ID
|
||||
#[serde(default)]
|
||||
pub album_id: u64,
|
||||
/// Album name
|
||||
#[serde(default)]
|
||||
pub album_name: String,
|
||||
|
||||
/// Musixmatch artist ID
|
||||
pub artist_id: u64,
|
||||
/// Musicbrainz artist ID
|
||||
///
|
||||
/// **Note:** most tracks dont have this entry set
|
||||
#[serde(default)]
|
||||
pub artist_mbid: String,
|
||||
/// Artist name
|
||||
#[serde(default)]
|
||||
pub artist_name: String,
|
||||
|
||||
/// Album cover URL (100x100px)
|
||||
#[serde(default)]
|
||||
pub album_coverart_100x100: String,
|
||||
/// Album cover URL (350x350px)
|
||||
#[serde(default)]
|
||||
pub album_coverart_350x350: String,
|
||||
/// Album cover URL (500x500px)
|
||||
#[serde(default)]
|
||||
pub album_coverart_500x500: String,
|
||||
/// Album cover URL (800x800px)
|
||||
///
|
||||
/// **Note:** not present on a lot of albums
|
||||
#[serde(default)]
|
||||
pub album_coverart_800x800: String,
|
||||
|
||||
/// Human-readable Musixmatch ID
|
||||
///
|
||||
/// Used in the URLs on the Musixmatch website.
|
||||
///
|
||||
/// Example: `aespa/Black-Mamba`
|
||||
pub commontrack_vanity_id: String,
|
||||
|
||||
/// Track release date
|
||||
#[serde(default, deserialize_with = "crate::api_model::optional_date")]
|
||||
pub first_release_date: Option<DateTime<Utc>>,
|
||||
/// Date and time when the track was last updated
|
||||
pub updated_time: DateTime<Utc>,
|
||||
|
||||
/// List of primary genres
|
||||
#[serde(default)]
|
||||
pub primary_genres: Genres,
|
||||
/// Secondary genres / Subgenres
|
||||
///
|
||||
/// Example: primary_genres: Pop, secondary_genres: K-Pop / Mandopop
|
||||
///
|
||||
/// Note that this schema is not applied to all tracks on Musixmatch.
|
||||
/// There are for example many K-Pop tracks with both Pop and K-Pop
|
||||
/// tagged as primary genre and this field empty.
|
||||
#[serde(default)]
|
||||
pub secondary_genres: Genres,
|
||||
|
||||
/// Status of lyrics translation
|
||||
#[serde(default)]
|
||||
pub track_lyrics_translation_status: Vec<TrackLyricsTranslationStatus>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub struct TrackLyricsTranslationStatus {
|
||||
/// Source language code (e.g. "ko")
|
||||
pub from: String,
|
||||
/// Target language code (e.g. "en")
|
||||
pub to: String,
|
||||
/// Translation ratio from 0 (untranslated) - 1 (fully translated)
|
||||
///
|
||||
/// **NOT** the percentage
|
||||
pub perc: f32,
|
||||
}
|
||||
|
||||
/// Available track charts
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum ChartName {
|
||||
/// Editorial chart
|
||||
Top,
|
||||
/// Most viewed lyrics in the last 2 hours
|
||||
Hot,
|
||||
}
|
||||
|
||||
impl ChartName {
|
||||
pub(crate) fn as_str(&self) -> &str {
|
||||
match self {
|
||||
ChartName::Top => "top",
|
||||
ChartName::Hot => "hot",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SortOrder {
|
||||
Asc,
|
||||
Desc,
|
||||
}
|
||||
|
||||
impl SortOrder {
|
||||
pub(crate) fn as_str(&self) -> &str {
|
||||
match self {
|
||||
SortOrder::Asc => "asc",
|
||||
SortOrder::Desc => "desc",
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
pub enum TrackId<'a> {
|
||||
/// Musixmatch ID for a unified track
|
||||
Commontrack(u64),
|
||||
/// Unique Musixmatch track ID
|
||||
TrackId(u64),
|
||||
/// Human-readable Musixmatch ID
|
||||
///
|
||||
/// Used in the URLs on the Musixmatch website.
|
||||
///
|
||||
/// Example: `aespa/Black-Mamba`
|
||||
CommontrackVanity(&'a str),
|
||||
/// [International Standard Recording Code](https://en.wikipedia.org/wiki/International_Standard_Recording_Code)
|
||||
Isrc(&'a str),
|
||||
/// Musicbrainz ID
|
||||
///
|
||||
/// **Note:** Musicbrainz IDs are not present for most tracks in
|
||||
/// the Musixmatch database, so use them only with a fallback.
|
||||
Musicbrainz(&'a str),
|
||||
/// Spotify track ID
|
||||
Spotify(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> TrackId<'a> {
|
||||
pub(crate) fn to_param(&self) -> (&'static str, String) {
|
||||
match &self {
|
||||
// &["commontrack_id", &id.to_string()]
|
||||
TrackId::Commontrack(id) => ("commontrack_id", id.to_string()),
|
||||
TrackId::TrackId(id) => ("track_id", id.to_string()),
|
||||
TrackId::CommontrackVanity(id) => ("commontrack_vanity_id", id.to_string()),
|
||||
TrackId::Isrc(id) => ("track_isrc", id.to_string()),
|
||||
TrackId::Musicbrainz(id) => ("track_mbid", id.to_string()),
|
||||
TrackId::Spotify(id) => ("track_spotify_id", id.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
123
tests/album_test.rs
Normal file
123
tests/album_test.rs
Normal file
|
@ -0,0 +1,123 @@
|
|||
use chrono::TimeZone;
|
||||
use dotenv::dotenv;
|
||||
use musixmatch_inofficial::{
|
||||
models::{AlbumId, ArtistId},
|
||||
storage::FileStorage,
|
||||
Error, Musixmatch,
|
||||
};
|
||||
use rstest::rstest;
|
||||
|
||||
#[ctor::ctor]
|
||||
fn init() {
|
||||
let _ = dotenv();
|
||||
env_logger::init();
|
||||
}
|
||||
|
||||
fn new_mxm() -> Musixmatch {
|
||||
Musixmatch::new(
|
||||
&std::env::var("MUSIXMATCH_EMAIL").unwrap(),
|
||||
&std::env::var("MUSIXMATCH_PASSWORD").unwrap(),
|
||||
Some(Box::new(FileStorage::default())),
|
||||
)
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::id(AlbumId::AlbumId(14323438))]
|
||||
#[case::musicbrainz(AlbumId::Musicbrainz("0fa2a7db-d26b-412e-beb0-df9855818b54"))]
|
||||
#[tokio::test]
|
||||
async fn by_id(#[case] album_id: AlbumId<'_>) {
|
||||
let album = new_mxm().album(album_id).await.unwrap();
|
||||
|
||||
assert_eq!(album.album_id, 14323438);
|
||||
assert_eq!(album.album_mbid, "0fa2a7db-d26b-412e-beb0-df9855818b54");
|
||||
assert_eq!(album.album_name, "Gangnam Style - Single");
|
||||
assert!(album.album_rating > 50);
|
||||
assert_eq!(album.album_track_count, 1);
|
||||
assert_eq!(album.album_release_date, "2012-01-01");
|
||||
assert_eq!(album.album_release_type, "Single");
|
||||
assert_eq!(album.artist_id, 410698);
|
||||
assert_eq!(album.artist_name, "Psy");
|
||||
|
||||
let first_pri_genre = &album.primary_genres.music_genre_list[0].music_genre;
|
||||
assert_eq!(first_pri_genre.music_genre_id, 14);
|
||||
assert_eq!(first_pri_genre.music_genre_parent_id, 34);
|
||||
assert_eq!(first_pri_genre.music_genre_name, "Pop");
|
||||
assert_eq!(first_pri_genre.music_genre_name_extended, "Pop");
|
||||
assert_eq!(first_pri_genre.music_genre_vanity.as_ref().unwrap(), "Pop");
|
||||
|
||||
let first_sec_genre = &album.secondary_genres.music_genre_list[0].music_genre;
|
||||
assert_eq!(first_sec_genre.music_genre_id, 17);
|
||||
assert_eq!(first_sec_genre.music_genre_parent_id, 34);
|
||||
assert_eq!(first_sec_genre.music_genre_name, "Dance");
|
||||
assert_eq!(first_sec_genre.music_genre_name_extended, "Dance");
|
||||
assert_eq!(
|
||||
first_sec_genre.music_genre_vanity.as_ref().unwrap(),
|
||||
"Dance"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
album.album_copyright,
|
||||
"℗ 2012 Schoolboy/Universal Republic Records, a division of UMG Recordings, Inc."
|
||||
);
|
||||
assert_eq!(
|
||||
album.album_label,
|
||||
"Silent Records/Universal Republic Records"
|
||||
);
|
||||
assert_eq!(album.album_vanity_id, "410698/Gangnam-Style-Single");
|
||||
assert!(
|
||||
album.updated_time
|
||||
> chrono::Utc.from_utc_datetime(&chrono::NaiveDateTime::new(
|
||||
chrono::NaiveDate::from_ymd(2022, 6, 3),
|
||||
chrono::NaiveTime::default(),
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
album.album_coverart_100x100,
|
||||
"https://s.mxmcdn.net/images-storage/albums/5/3/0/5/4/2/14245035.jpg"
|
||||
);
|
||||
assert_eq!(
|
||||
album.album_coverart_350x350,
|
||||
"https://s.mxmcdn.net/images-storage/albums/5/3/0/5/4/2/14245035_350_350.jpg"
|
||||
);
|
||||
assert_eq!(
|
||||
album.album_coverart_500x500,
|
||||
"https://s.mxmcdn.net/images-storage/albums/5/3/0/5/4/2/14245035_500_500.jpg"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn by_id_missing() {
|
||||
let err = new_mxm()
|
||||
.album(AlbumId::AlbumId(999999999999))
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(err, Error::NotFound));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn artist_albums() {
|
||||
let albums = new_mxm()
|
||||
.artist_albums(ArtistId::ArtistId(1039), None, 10, 1)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(albums.len(), 10);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn artist_albums_missing() {
|
||||
let err = new_mxm()
|
||||
.artist_albums(ArtistId::ArtistId(999999999999), None, 10, 1)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(err, Error::NotFound));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn charts() {
|
||||
let albums = new_mxm().chart_albums("US", 10, 1).await.unwrap();
|
||||
|
||||
assert_eq!(albums.len(), 10);
|
||||
}
|
150
tests/artist_test.rs
Normal file
150
tests/artist_test.rs
Normal file
|
@ -0,0 +1,150 @@
|
|||
use chrono::TimeZone;
|
||||
use dotenv::dotenv;
|
||||
use musixmatch_inofficial::{models::ArtistId, storage::FileStorage, Error, Musixmatch};
|
||||
use rstest::rstest;
|
||||
|
||||
#[ctor::ctor]
|
||||
fn init() {
|
||||
let _ = dotenv();
|
||||
env_logger::init();
|
||||
}
|
||||
|
||||
fn new_mxm() -> Musixmatch {
|
||||
Musixmatch::new(
|
||||
&std::env::var("MUSIXMATCH_EMAIL").unwrap(),
|
||||
&std::env::var("MUSIXMATCH_PASSWORD").unwrap(),
|
||||
Some(Box::new(FileStorage::default())),
|
||||
)
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::id(ArtistId::ArtistId(410698))]
|
||||
#[case::musicbrainz(ArtistId::Musicbrainz("f99b7d67-4e63-4678-aa66-4c6ac0f7d24a"))]
|
||||
#[tokio::test]
|
||||
async fn by_id(#[case] artist_id: ArtistId<'_>) {
|
||||
let artist = new_mxm().artist(artist_id).await.unwrap();
|
||||
|
||||
// dbg!(&artist);
|
||||
|
||||
assert_eq!(artist.artist_id, 410698);
|
||||
assert_eq!(artist.artist_mbid, "f99b7d67-4e63-4678-aa66-4c6ac0f7d24a");
|
||||
assert_eq!(artist.artist_name, "Psy");
|
||||
assert_eq!(
|
||||
artist.artist_name_translation_list[0]
|
||||
.artist_name_translation
|
||||
.language,
|
||||
"EN"
|
||||
);
|
||||
assert_eq!(
|
||||
artist.artist_name_translation_list[0]
|
||||
.artist_name_translation
|
||||
.translation,
|
||||
"PSY"
|
||||
);
|
||||
assert_eq!(
|
||||
artist.artist_name_translation_list[1]
|
||||
.artist_name_translation
|
||||
.language,
|
||||
"KO"
|
||||
);
|
||||
assert_eq!(
|
||||
artist.artist_name_translation_list[1]
|
||||
.artist_name_translation
|
||||
.translation,
|
||||
"싸이"
|
||||
);
|
||||
assert_eq!(artist.artist_country, "KR");
|
||||
assert!(artist.artist_rating > 50);
|
||||
let first_genre = &artist.primary_genres.music_genre_list[0].music_genre;
|
||||
assert_eq!(first_genre.music_genre_id, 14);
|
||||
assert_eq!(first_genre.music_genre_parent_id, 34);
|
||||
assert_eq!(first_genre.music_genre_name, "Pop");
|
||||
assert_eq!(first_genre.music_genre_name_extended, "Pop");
|
||||
assert_eq!(first_genre.music_genre_vanity.as_ref().unwrap(), "Pop");
|
||||
assert_eq!(artist.artist_twitter_url, "https://twitter.com/psy_oppa");
|
||||
assert_eq!(
|
||||
artist.artist_facebook_url,
|
||||
"https://www.facebook.com/officialpsy"
|
||||
);
|
||||
assert_eq!(artist.artist_vanity_id, "410698");
|
||||
assert!(
|
||||
artist.updated_time
|
||||
> chrono::Utc.from_utc_datetime(&chrono::NaiveDateTime::new(
|
||||
chrono::NaiveDate::from_ymd(2016, 6, 30),
|
||||
chrono::NaiveTime::default(),
|
||||
))
|
||||
);
|
||||
assert_eq!(artist.begin_date_year, "1977");
|
||||
assert_eq!(artist.begin_date, "1977-12-31");
|
||||
assert_eq!(artist.end_date_year, "");
|
||||
assert_eq!(artist.end_date, "0000-00-00");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn by_id_missing() {
|
||||
let err = new_mxm()
|
||||
.artist(ArtistId::ArtistId(999999999999))
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(err, Error::NotFound));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn related() {
|
||||
let artists = new_mxm()
|
||||
.artist_related(ArtistId::ArtistId(26485840), 10, 1)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(artists.len(), 10);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn related_missing() {
|
||||
let err = new_mxm()
|
||||
.artist_related(ArtistId::ArtistId(999999999999), 10, 1)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(err, Error::NotFound));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn search() {
|
||||
let artists = new_mxm().artist_search("psy", 5, 1).await.unwrap();
|
||||
|
||||
assert_eq!(artists.len(), 5);
|
||||
|
||||
let artist = &artists[0];
|
||||
assert_eq!(artist.artist_id, 410698);
|
||||
assert_eq!(artist.artist_name, "Psy");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn search_empty() {
|
||||
let artists = new_mxm()
|
||||
.artist_search(
|
||||
"Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz",
|
||||
5,
|
||||
1,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(artists.len(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn charts() {
|
||||
let artists = new_mxm().chart_artists("US", 10, 1).await.unwrap();
|
||||
|
||||
assert_eq!(artists.len(), 10);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn charts_no_country() {
|
||||
let artists = new_mxm().chart_artists("XY", 10, 1).await.unwrap();
|
||||
|
||||
assert_eq!(artists.len(), 10);
|
||||
}
|
102
tests/lyrics_test.rs
Normal file
102
tests/lyrics_test.rs
Normal file
|
@ -0,0 +1,102 @@
|
|||
use chrono::TimeZone;
|
||||
use dotenv::dotenv;
|
||||
use musixmatch_inofficial::{models::TrackId, storage::FileStorage, Error, Musixmatch};
|
||||
use rstest::rstest;
|
||||
|
||||
#[ctor::ctor]
|
||||
fn init() {
|
||||
let _ = dotenv();
|
||||
env_logger::init();
|
||||
}
|
||||
|
||||
fn new_mxm() -> Musixmatch {
|
||||
Musixmatch::new(
|
||||
&std::env::var("MUSIXMATCH_EMAIL").unwrap(),
|
||||
&std::env::var("MUSIXMATCH_PASSWORD").unwrap(),
|
||||
Some(Box::new(FileStorage::default())),
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn from_match() {
|
||||
let lyrics = new_mxm().matcher_lyrics("Shine", "Spektrem").await.unwrap();
|
||||
|
||||
// dbg!(&lyrics);
|
||||
|
||||
assert_eq!(lyrics.lyrics_id, 25947036);
|
||||
assert!(!lyrics.instrumental);
|
||||
assert!(!lyrics.explicit);
|
||||
assert!(lyrics
|
||||
.lyrics_body
|
||||
.starts_with("Eyes, in the sky, gazing far into the night\n"));
|
||||
assert_eq!(lyrics.lyrics_language, "en");
|
||||
assert_eq!(lyrics.lyrics_language_description, "English");
|
||||
assert!(lyrics.lyrics_copyright.contains("Kim Jeffeson"));
|
||||
assert!(
|
||||
lyrics.updated_time
|
||||
> chrono::Utc.from_utc_datetime(&chrono::NaiveDateTime::new(
|
||||
chrono::NaiveDate::from_ymd(2021, 6, 30),
|
||||
chrono::NaiveTime::default(),
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::trackid(TrackId::TrackId(205688271))]
|
||||
#[case::commontrack(TrackId::Commontrack(118480583))]
|
||||
#[case::vanity(TrackId::CommontrackVanity("aespa/Black-Mamba"))]
|
||||
#[case::isrc(TrackId::Isrc("KRA302000590"))]
|
||||
#[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51"))]
|
||||
#[tokio::test]
|
||||
async fn from_id(#[case] track_id: TrackId<'_>) {
|
||||
let lyrics = new_mxm().track_lyrics(track_id).await.unwrap();
|
||||
|
||||
// dbg!(&lyrics);
|
||||
|
||||
assert_eq!(lyrics.lyrics_id, 29401691);
|
||||
assert_eq!(lyrics.lyrics_language, "ko");
|
||||
assert_eq!(lyrics.lyrics_language_description, "Korean");
|
||||
assert!(lyrics.lyrics_copyright.contains("Michael Fonseca"));
|
||||
assert!(
|
||||
lyrics.updated_time
|
||||
> chrono::Utc.from_utc_datetime(&chrono::NaiveDateTime::new(
|
||||
chrono::NaiveDate::from_ymd(2022, 8, 27),
|
||||
chrono::NaiveTime::default(),
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/// This track has no lyrics
|
||||
#[tokio::test]
|
||||
async fn instrumental() {
|
||||
let lyrics = new_mxm()
|
||||
.matcher_lyrics("drivers license", "Bobby G")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(lyrics.lyrics_id, 25891609);
|
||||
assert!(lyrics.instrumental);
|
||||
assert!(!lyrics.explicit);
|
||||
assert_eq!(lyrics.lyrics_body, "");
|
||||
assert_eq!(lyrics.lyrics_language, "");
|
||||
assert_eq!(lyrics.lyrics_language_description, "");
|
||||
assert_eq!(lyrics.lyrics_copyright, "");
|
||||
assert!(
|
||||
lyrics.updated_time
|
||||
> chrono::Utc.from_utc_datetime(&chrono::NaiveDateTime::new(
|
||||
chrono::NaiveDate::from_ymd(2021, 6, 21),
|
||||
chrono::NaiveTime::default(),
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/// This track has no lyrics
|
||||
#[tokio::test]
|
||||
async fn missing() {
|
||||
let err = new_mxm()
|
||||
.track_lyrics(TrackId::Spotify("674JwwTP7xCje81T0DRrLn"))
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(err, Error::NotFound));
|
||||
}
|
|
@ -37,8 +37,8 @@ async fn from_match() {
|
|||
// dbg!(&subtitle);
|
||||
|
||||
assert_eq!(subtitle.subtitle_id, 35340319);
|
||||
assert_eq!(subtitle.subtitle_language.unwrap(), "en");
|
||||
assert_eq!(subtitle.subtitle_language_description.unwrap(), "English");
|
||||
assert_eq!(subtitle.subtitle_language, "en");
|
||||
assert_eq!(subtitle.subtitle_language_description, "English");
|
||||
assert!(subtitle.lyrics_copyright.contains("Kim Jeffeson"));
|
||||
assert_eq!(subtitle.subtitle_length, 316);
|
||||
assert!(
|
||||
|
@ -66,8 +66,8 @@ async fn from_id(#[case] track_id: TrackId<'_>) {
|
|||
// dbg!(&subtitle);
|
||||
|
||||
assert_eq!(subtitle.subtitle_id, 36476905);
|
||||
assert_eq!(subtitle.subtitle_language.unwrap(), "ko");
|
||||
assert_eq!(subtitle.subtitle_language_description.unwrap(), "Korean");
|
||||
assert_eq!(subtitle.subtitle_language, "ko");
|
||||
assert_eq!(subtitle.subtitle_language_description, "Korean");
|
||||
assert!(subtitle.lyrics_copyright.contains("Michael Fonseca"));
|
||||
assert_eq!(subtitle.subtitle_length, 175);
|
||||
assert!(
|
||||
|
@ -95,3 +95,35 @@ async fn instrumental() {
|
|||
|
||||
assert!(matches!(err, Error::NotFound));
|
||||
}
|
||||
|
||||
/// This track has not been synced
|
||||
#[tokio::test]
|
||||
async fn unsynced() {
|
||||
let err = new_mxm()
|
||||
.track_subtitle(
|
||||
TrackId::Spotify("6oaWIABGL7eeiMILEDyGX1"),
|
||||
SubtitleFormat::Json,
|
||||
Some(213.0),
|
||||
Some(1.0),
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(err, Error::NotFound));
|
||||
}
|
||||
|
||||
/// Try to get subtitles with wrong length parameter
|
||||
#[tokio::test]
|
||||
async fn wrong_length() {
|
||||
let err = new_mxm()
|
||||
.track_subtitle(
|
||||
TrackId::Commontrack(118480583),
|
||||
SubtitleFormat::Json,
|
||||
Some(200.0),
|
||||
Some(1.0),
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(err, Error::NotFound));
|
||||
}
|
458
tests/track_test.rs
Normal file
458
tests/track_test.rs
Normal file
|
@ -0,0 +1,458 @@
|
|||
use chrono::{Datelike, TimeZone};
|
||||
use dotenv::dotenv;
|
||||
use musixmatch_inofficial::{
|
||||
models::{AlbumId, ChartName, SortOrder, TrackId},
|
||||
storage::FileStorage,
|
||||
Error, Musixmatch,
|
||||
};
|
||||
use rstest::rstest;
|
||||
|
||||
#[ctor::ctor]
|
||||
fn init() {
|
||||
let _ = dotenv();
|
||||
env_logger::init();
|
||||
}
|
||||
|
||||
fn new_mxm() -> Musixmatch {
|
||||
Musixmatch::new(
|
||||
&std::env::var("MUSIXMATCH_EMAIL").unwrap(),
|
||||
&std::env::var("MUSIXMATCH_PASSWORD").unwrap(),
|
||||
Some(Box::new(FileStorage::default())),
|
||||
)
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::no_translation(false, false)]
|
||||
#[case::translation_2c(true, false)]
|
||||
#[case::translation_3c(true, true)]
|
||||
#[tokio::test]
|
||||
async fn from_match(#[case] translation_status: bool, #[case] lang_3c: bool) {
|
||||
let track = new_mxm()
|
||||
.matcher_track("Gangnam Style", "PSY", "", translation_status, lang_3c)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
dbg!(&track);
|
||||
|
||||
assert_eq!(track.track_id, 19737449);
|
||||
assert_eq!(track.track_mbid, "882ce25d-51b9-4fe5-bbdf-16e661df0822");
|
||||
assert_eq!(track.track_isrc, "USUM71210283");
|
||||
assert_eq!(
|
||||
track.commontrack_isrcs,
|
||||
vec![vec![
|
||||
"USUM71210283",
|
||||
"USHM91235871",
|
||||
"KRA341205652",
|
||||
"DEN061202418"
|
||||
]]
|
||||
);
|
||||
assert_eq!(track.track_spotify_id, "1PKnaWkakd5CBjNv8NyaSK");
|
||||
assert_eq!(
|
||||
track.commontrack_spotify_ids,
|
||||
vec![
|
||||
"1PKnaWkakd5CBjNv8NyaSK",
|
||||
"0TN8agMRmMu9oh2UbUbmMr",
|
||||
"4htXSyLAH1wcHuLA5PKHSk",
|
||||
"1d6RiDRVLe2RS5N3faTm4A",
|
||||
"3KfAWiIGR5jaihyB7cMZtg",
|
||||
"291iUZHZVkDUTs6rHXJ1bx",
|
||||
"3HtU8IlHuHbL37fOB60sQ1",
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
track.track_xboxmusic_id,
|
||||
"music.7B42BF07-0100-11DB-89CA-0019B92A3933"
|
||||
);
|
||||
assert_eq!(track.track_name, "Gangnam Style");
|
||||
assert!(track.track_rating > 50);
|
||||
assert_eq!(track.commontrack_id, 86989384);
|
||||
assert!(!track.instrumental);
|
||||
assert!(track.explicit);
|
||||
assert!(track.has_subtitles);
|
||||
assert!(track.has_track_structure);
|
||||
assert!(track.num_favourite > 140);
|
||||
assert_eq!(track.lyrics_id, 29727716);
|
||||
assert_eq!(track.subtitle_id, 36671981);
|
||||
assert_eq!(track.album_id, 14323438);
|
||||
assert_eq!(track.album_name, "Gangnam Style - Single");
|
||||
assert_eq!(track.artist_id, 410698);
|
||||
assert_eq!(track.artist_mbid, "f99b7d67-4e63-4678-aa66-4c6ac0f7d24a");
|
||||
assert_eq!(track.artist_name, "Psy");
|
||||
assert_eq!(
|
||||
track.album_coverart_100x100,
|
||||
"https://s.mxmcdn.net/images-storage/albums/5/3/0/5/4/2/14245035.jpg"
|
||||
);
|
||||
assert_eq!(
|
||||
track.album_coverart_350x350,
|
||||
"https://s.mxmcdn.net/images-storage/albums/5/3/0/5/4/2/14245035_350_350.jpg"
|
||||
);
|
||||
assert_eq!(
|
||||
track.album_coverart_500x500,
|
||||
"https://s.mxmcdn.net/images-storage/albums/5/3/0/5/4/2/14245035_500_500.jpg"
|
||||
);
|
||||
assert_eq!(track.commontrack_vanity_id, "410698/gangnam-style");
|
||||
let first_release = track.first_release_date.unwrap();
|
||||
assert_eq!(first_release.day(), 1);
|
||||
assert_eq!(first_release.month(), 1);
|
||||
assert_eq!(first_release.year(), 2012);
|
||||
assert!(
|
||||
track.updated_time
|
||||
> chrono::Utc.from_utc_datetime(&chrono::NaiveDateTime::new(
|
||||
chrono::NaiveDate::from_ymd(2022, 9, 25),
|
||||
chrono::NaiveTime::default(),
|
||||
))
|
||||
);
|
||||
|
||||
let first_pri_genre = &track.primary_genres.music_genre_list[0].music_genre;
|
||||
assert_eq!(first_pri_genre.music_genre_id, 14);
|
||||
assert_eq!(first_pri_genre.music_genre_parent_id, 34);
|
||||
assert_eq!(first_pri_genre.music_genre_name, "Pop");
|
||||
assert_eq!(first_pri_genre.music_genre_name_extended, "Pop");
|
||||
assert_eq!(first_pri_genre.music_genre_vanity.as_ref().unwrap(), "Pop");
|
||||
|
||||
let first_sec_genre = &track.secondary_genres.music_genre_list[0].music_genre;
|
||||
assert_eq!(first_sec_genre.music_genre_id, 51);
|
||||
assert_eq!(first_sec_genre.music_genre_parent_id, 34);
|
||||
assert_eq!(first_sec_genre.music_genre_name, "K-Pop");
|
||||
assert_eq!(first_sec_genre.music_genre_name_extended, "K-Pop");
|
||||
assert_eq!(
|
||||
first_sec_genre.music_genre_vanity.as_ref().unwrap(),
|
||||
"K-Pop"
|
||||
);
|
||||
|
||||
if translation_status {
|
||||
let first_tstatus = &track.track_lyrics_translation_status[0];
|
||||
if lang_3c {
|
||||
assert_eq!(first_tstatus.from, "kor");
|
||||
} else {
|
||||
assert_eq!(first_tstatus.from, "ko");
|
||||
}
|
||||
assert!(first_tstatus.perc >= 0.0 && first_tstatus.perc <= 1.0);
|
||||
} else {
|
||||
assert!(track.track_lyrics_translation_status.is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::trackid(TrackId::TrackId(205688271))]
|
||||
#[case::commontrack(TrackId::Commontrack(118480583))]
|
||||
#[case::vanity(TrackId::CommontrackVanity("aespa/Black-Mamba"))]
|
||||
#[case::isrc(TrackId::Isrc("KRA302000590"))]
|
||||
#[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51"))]
|
||||
#[tokio::test]
|
||||
async fn from_id(#[case] track_id: TrackId<'_>) {
|
||||
let track = new_mxm().track(track_id, true, false).await.unwrap();
|
||||
|
||||
// dbg!(&track);
|
||||
|
||||
assert_eq!(track.track_id, 205688271);
|
||||
assert_eq!(track.track_isrc, "KRA302000590");
|
||||
assert_eq!(track.track_spotify_id, "1t2qYCAjUAoGfeFeoBlK51");
|
||||
assert_eq!(track.track_name, "Black Mamba");
|
||||
assert!(track.track_rating > 50);
|
||||
assert_eq!(track.track_length, 175);
|
||||
assert!(!track.explicit);
|
||||
assert!(track.has_lyrics);
|
||||
assert!(track.has_subtitles);
|
||||
assert!(track.has_richsync);
|
||||
assert!(track.num_favourite > 200);
|
||||
assert_eq!(track.lyrics_id, 29401691);
|
||||
assert_eq!(track.subtitle_id, 36476905);
|
||||
assert_eq!(track.album_id, 41035954);
|
||||
assert_eq!(track.album_name, "Black Mamba - Single");
|
||||
assert_eq!(track.artist_id, 46970441);
|
||||
assert_eq!(track.artist_name, "aespa");
|
||||
assert_eq!(
|
||||
track.album_coverart_100x100,
|
||||
"https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772.jpg"
|
||||
);
|
||||
assert_eq!(
|
||||
track.album_coverart_350x350,
|
||||
"https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772_350_350.jpg"
|
||||
);
|
||||
assert_eq!(
|
||||
track.album_coverart_500x500,
|
||||
"https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772_500_500.jpg"
|
||||
);
|
||||
assert_eq!(track.commontrack_vanity_id, "aespa/Black-Mamba");
|
||||
|
||||
let release_date = track.first_release_date.unwrap();
|
||||
assert_eq!(release_date.day(), 17);
|
||||
assert_eq!(release_date.month(), 11);
|
||||
assert_eq!(release_date.year(), 2020);
|
||||
assert!(
|
||||
track.updated_time
|
||||
> chrono::Utc.from_utc_datetime(&chrono::NaiveDateTime::new(
|
||||
chrono::NaiveDate::from_ymd(2022, 8, 27),
|
||||
chrono::NaiveTime::default(),
|
||||
))
|
||||
);
|
||||
|
||||
let first_tstatus = &track.track_lyrics_translation_status[0];
|
||||
assert_eq!(first_tstatus.from, "ko");
|
||||
assert!(first_tstatus.perc >= 0.0 && first_tstatus.perc <= 1.0);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::no_translation(false, false)]
|
||||
#[case::translation_2c(true, false)]
|
||||
#[case::translation_3c(true, true)]
|
||||
#[tokio::test]
|
||||
async fn from_id_translations(#[case] translation_status: bool, #[case] lang_3c: bool) {
|
||||
let track = new_mxm()
|
||||
.track(TrackId::TrackId(19737449), translation_status, lang_3c)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
dbg!(&track);
|
||||
|
||||
assert_eq!(track.track_id, 19737449);
|
||||
assert_eq!(track.track_mbid, "882ce25d-51b9-4fe5-bbdf-16e661df0822");
|
||||
assert_eq!(track.track_isrc, "USUM71210283");
|
||||
assert_eq!(
|
||||
track.commontrack_isrcs,
|
||||
vec![vec![
|
||||
"USUM71210283",
|
||||
"USHM91235871",
|
||||
"KRA341205652",
|
||||
"DEN061202418"
|
||||
]]
|
||||
);
|
||||
assert_eq!(track.track_spotify_id, "1PKnaWkakd5CBjNv8NyaSK");
|
||||
assert_eq!(
|
||||
track.commontrack_spotify_ids,
|
||||
vec![
|
||||
"1PKnaWkakd5CBjNv8NyaSK",
|
||||
"0TN8agMRmMu9oh2UbUbmMr",
|
||||
"4htXSyLAH1wcHuLA5PKHSk",
|
||||
"1d6RiDRVLe2RS5N3faTm4A",
|
||||
"3KfAWiIGR5jaihyB7cMZtg",
|
||||
"291iUZHZVkDUTs6rHXJ1bx",
|
||||
"3HtU8IlHuHbL37fOB60sQ1",
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
track.track_xboxmusic_id,
|
||||
"music.7B42BF07-0100-11DB-89CA-0019B92A3933"
|
||||
);
|
||||
assert_eq!(track.track_name, "Gangnam Style");
|
||||
assert!(track.track_rating > 50);
|
||||
assert_eq!(track.commontrack_id, 86989384);
|
||||
assert!(!track.instrumental);
|
||||
assert!(track.explicit);
|
||||
assert!(track.has_subtitles);
|
||||
assert!(track.has_track_structure);
|
||||
assert!(track.num_favourite > 140);
|
||||
assert_eq!(track.lyrics_id, 29727716);
|
||||
assert_eq!(track.subtitle_id, 36671981);
|
||||
assert_eq!(track.album_id, 14323438);
|
||||
assert_eq!(track.album_name, "Gangnam Style - Single");
|
||||
assert_eq!(track.artist_id, 410698);
|
||||
assert_eq!(track.artist_mbid, "f99b7d67-4e63-4678-aa66-4c6ac0f7d24a");
|
||||
assert_eq!(track.artist_name, "Psy");
|
||||
assert_eq!(
|
||||
track.album_coverart_100x100,
|
||||
"https://s.mxmcdn.net/images-storage/albums/5/3/0/5/4/2/14245035.jpg"
|
||||
);
|
||||
assert_eq!(
|
||||
track.album_coverart_350x350,
|
||||
"https://s.mxmcdn.net/images-storage/albums/5/3/0/5/4/2/14245035_350_350.jpg"
|
||||
);
|
||||
assert_eq!(
|
||||
track.album_coverart_500x500,
|
||||
"https://s.mxmcdn.net/images-storage/albums/5/3/0/5/4/2/14245035_500_500.jpg"
|
||||
);
|
||||
assert_eq!(track.commontrack_vanity_id, "410698/gangnam-style");
|
||||
|
||||
let first_release = track.first_release_date.unwrap();
|
||||
assert_eq!(first_release.day(), 1);
|
||||
assert_eq!(first_release.month(), 1);
|
||||
assert_eq!(first_release.year(), 2012);
|
||||
assert!(
|
||||
track.updated_time
|
||||
> chrono::Utc.from_utc_datetime(&chrono::NaiveDateTime::new(
|
||||
chrono::NaiveDate::from_ymd(2022, 9, 25),
|
||||
chrono::NaiveTime::default(),
|
||||
))
|
||||
);
|
||||
|
||||
let first_pri_genre = &track.primary_genres.music_genre_list[0].music_genre;
|
||||
assert_eq!(first_pri_genre.music_genre_id, 14);
|
||||
assert_eq!(first_pri_genre.music_genre_parent_id, 34);
|
||||
assert_eq!(first_pri_genre.music_genre_name, "Pop");
|
||||
assert_eq!(first_pri_genre.music_genre_name_extended, "Pop");
|
||||
assert_eq!(first_pri_genre.music_genre_vanity.as_ref().unwrap(), "Pop");
|
||||
|
||||
let first_sec_genre = &track.secondary_genres.music_genre_list[0].music_genre;
|
||||
assert_eq!(first_sec_genre.music_genre_id, 51);
|
||||
assert_eq!(first_sec_genre.music_genre_parent_id, 34);
|
||||
assert_eq!(first_sec_genre.music_genre_name, "K-Pop");
|
||||
assert_eq!(first_sec_genre.music_genre_name_extended, "K-Pop");
|
||||
assert_eq!(
|
||||
first_sec_genre.music_genre_vanity.as_ref().unwrap(),
|
||||
"K-Pop"
|
||||
);
|
||||
|
||||
if translation_status {
|
||||
let first_tstatus = &track.track_lyrics_translation_status[0];
|
||||
if lang_3c {
|
||||
assert_eq!(first_tstatus.from, "kor");
|
||||
} else {
|
||||
assert_eq!(first_tstatus.from, "ko");
|
||||
}
|
||||
assert!(first_tstatus.perc >= 0.0 && first_tstatus.perc <= 1.0);
|
||||
} else {
|
||||
assert!(track.track_lyrics_translation_status.is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn from_id_missing() {
|
||||
let err = new_mxm()
|
||||
.track(TrackId::TrackId(999999999999), false, false)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(err, Error::NotFound));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn album_tracks() {
|
||||
let tracks = new_mxm()
|
||||
.album_tracks(AlbumId::AlbumId(17118624), true, 20, 1)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// dbg!(&tracks);
|
||||
|
||||
let track_names = tracks
|
||||
.iter()
|
||||
.map(|t| t.track_name.to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(
|
||||
track_names,
|
||||
vec![
|
||||
"Gäa",
|
||||
"Vergiss mein nicht",
|
||||
"Orome",
|
||||
"Falke flieg",
|
||||
"Minne",
|
||||
"Das Lied der Ahnen",
|
||||
"Hörst du den Wind",
|
||||
"Nan Úye",
|
||||
"Faolan",
|
||||
"Hymne der Nacht",
|
||||
"Avalon",
|
||||
"Tolo Nan",
|
||||
"Oonagh",
|
||||
]
|
||||
);
|
||||
|
||||
tracks.iter().for_each(|t| {
|
||||
assert!(t.has_lyrics);
|
||||
assert!(t.has_subtitles);
|
||||
});
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn album_missing() {
|
||||
let err = new_mxm()
|
||||
.album_tracks(AlbumId::AlbumId(999999999999), false, 20, 1)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(err, Error::NotFound));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::top(ChartName::Top)]
|
||||
#[case::hot(ChartName::Hot)]
|
||||
#[tokio::test]
|
||||
async fn charts(#[case] chart_name: ChartName) {
|
||||
let tracks = new_mxm()
|
||||
.chart_tracks("US", chart_name, true, 20, 1)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(tracks.len(), 20);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn search() {
|
||||
let tracks = new_mxm()
|
||||
.track_search()
|
||||
.q_artist("Lena")
|
||||
.q_track("Satellite")
|
||||
.s_track_rating(SortOrder::Desc)
|
||||
.send(1, 0)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
dbg!(&tracks);
|
||||
|
||||
assert_eq!(tracks.len(), 1);
|
||||
|
||||
let track = &tracks[0];
|
||||
assert_eq!(track.commontrack_id, 12426476);
|
||||
assert_eq!(track.track_name, "Satellite");
|
||||
assert_eq!(track.artist_name, "Lena");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn search_lyrics() {
|
||||
let tracks = new_mxm()
|
||||
.track_search()
|
||||
.q_lyrics("not a thing that i would change")
|
||||
.s_track_rating(SortOrder::Desc)
|
||||
.send(10, 1)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(tracks.len(), 10);
|
||||
|
||||
let track = &tracks[0];
|
||||
assert_eq!(track.track_name, "Just the Way You Are");
|
||||
assert_eq!(track.artist_name, "Bruno Mars");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn search_empty() {
|
||||
let artists = new_mxm()
|
||||
.track_search()
|
||||
.q("Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz")
|
||||
.send(10, 1)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(artists.len(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn genres() {
|
||||
let genres = new_mxm().genres().await.unwrap();
|
||||
assert!(genres.len() > 360);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn snippet() {
|
||||
let snippet = new_mxm()
|
||||
.track_snippet(TrackId::Commontrack(8874280))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(snippet.snippet_id, 23036767);
|
||||
assert_eq!(snippet.snippet_language, "en");
|
||||
assert!(!snippet.instrumental);
|
||||
assert!(
|
||||
snippet.updated_time
|
||||
> chrono::Utc.from_utc_datetime(&chrono::NaiveDateTime::new(
|
||||
chrono::NaiveDate::from_ymd(2022, 8, 29),
|
||||
chrono::NaiveTime::default(),
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
snippet.snippet_body,
|
||||
"There's not a thing that I would change"
|
||||
);
|
||||
}
|
Loading…
Add table
Reference in a new issue