use crate::error::Error;
use crate::script_controller::{ParamType, ScriptController};
use serde::Deserialize;
use std::io::Read;
use urlencoding::encode;
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Track {
pub class: TrackKind,
pub id: i32,
pub index: i32,
pub name: String,
#[serde(rename = "persistentID")]
pub persistent_id: String,
pub album: String,
pub album_artist: String,
pub album_disliked: bool,
pub album_favorited: bool,
pub album_rating: Option<i16>,
pub album_rating_kind: Option<Kind>,
pub artworks_raw_data: Option<Vec<Artwork>>,
artwork_url: Option<String>,
pub artist: String,
pub bit_rate: Option<i16>,
pub bookmark: i8,
pub bookmarkable: bool,
pub bpm: i16,
pub category: String,
pub cloud_status: Option<CloudStatus>,
pub comment: String,
pub compilation: bool,
pub composer: String,
#[serde(rename = "databaseID")]
pub database_id: i32,
pub date_added: String,
pub description: String,
pub disliked: bool,
#[serde(rename = "downloaderAppleID")]
pub downloader_apple_id: Option<String>,
pub downloader_name: Option<String>,
pub duration: f64,
pub enabled: bool,
#[serde(rename = "episodeID")]
pub episode_id: Option<String>,
pub episode_number: i16,
pub eq: String,
pub finish: f64,
pub gapless: Option<bool>,
pub genre: String,
pub grouping: String,
pub kind: Option<String>,
pub long_description: Option<String>,
pub favorited: bool,
pub lyrics: Option<String>,
pub media_kind: MediaKind,
pub modification_date: Option<String>,
pub movement: Option<String>,
pub movement_count: i16,
pub movement_number: i16,
pub played_count: i16,
pub played_date: Option<String>,
#[serde(rename = "purchaserAppleID")]
pub purchaser_apple_id: Option<String>,
pub purchaser_name: Option<String>,
pub rating: i16,
pub rating_kind: Option<Kind>,
pub release_date: Option<String>,
pub sample_rate: Option<i32>,
pub season_number: Option<i16>,
pub shufflable: bool,
pub skipped_count: i16,
pub skipped_date: Option<String>,
pub show: Option<String>,
pub sort_album: Option<String>,
pub sort_artist: Option<String>,
pub sort_album_artist: Option<String>,
pub sort_name: Option<String>,
pub sort_composer: Option<String>,
pub sort_show: Option<String>,
pub size: Option<i64>,
pub start: f64,
pub time: String,
pub track_count: i16,
track_url: Option<String>,
pub track_number: i16,
pub unplayed: bool,
pub volume_adjustment: i16,
pub work: Option<String>,
pub year: i16,
}
impl Track {
pub fn artwork_url(&mut self) -> &Option<String> {
if self.artwork_url == None {
self.fetch_itunes_store_data()
}
return &self.artwork_url;
}
pub fn track_url(&mut self) -> &Option<String> {
if self.track_url == None {
self.fetch_itunes_store_data()
}
return &self.track_url;
}
pub fn fetch_artworks_raw_data(&mut self) -> Result<(), Error> {
match ScriptController.execute_script::<Vec<Artwork>>(
ParamType::Artworks,
Some(self.id),
None,
) {
Ok(data) => {
self.artworks_raw_data = Some(data);
Ok(())
}
Err(err) => Err(err),
}
}
pub fn reveal_in_player(&self) -> Result<(), Error> {
let cmd = format!(
"Application('Music').reveal(Application('Music').tracks.byId({}))",
self.id
);
let _ = ScriptController.execute(cmd.as_str(), None);
Ok(())
}
pub fn download(&self) -> Result<(), Error> {
let cmd = format!(
"Application('Music').download(Application('Music').tracks.byId({}))",
self.id
);
let _ = ScriptController.execute(cmd.as_str(), None);
Ok(())
}
pub fn set_favorited(&self, value: bool) -> Result<(), Error> {
let cmd = format!(
"Application('Music').tracks.byId({}).favorited = {}",
self.id, value
);
let _ = ScriptController.execute(cmd.as_str(), None);
Ok(())
}
pub fn set_disliked(&self, value: bool) -> Result<(), Error> {
let cmd = format!(
"Application('Music').tracks.byId({}).disliked = {}",
self.id, value
);
let _ = ScriptController.execute(cmd.as_str(), None);
Ok(())
}
fn fetch_itunes_store_data(&mut self) {
let request = format!(
"https://itunes.apple.com/search?term={}&entity=song&limit=200",
encode(self.name.as_str())
);
self.fetch_itunes_store_by_request(request);
if self.artwork_url == None {
let request = format!(
"https://itunes.apple.com/search?term={}&entity=song&attribute=albumTerm&limit=200",
encode(self.album.as_str())
);
self.fetch_itunes_store_by_request(request);
}
if self.artwork_url == None {
let request = format!(
"https://itunes.apple.com/search?term={}&entity=song&limit=200",
encode(self.artist.as_str())
);
self.fetch_itunes_store_by_request(request);
}
}
fn fetch_itunes_store_by_request(&mut self, request: String) {
let mut res = reqwest::blocking::get(request).unwrap();
let mut body = String::new();
res.read_to_string(&mut body).unwrap();
if let Ok(search) = serde_json::from_str::<ITunesStoreSearch>(body.as_str()) {
if search.result_count == 1 {
self.artwork_url = Some(search.results[0].clone().artwork_url_100);
self.track_url = Some(search.results[0].clone().track_view_url);
} else {
let result = search.results.iter().find(|result| {
(&result.track_name.to_lowercase() == &self.name.to_lowercase()
|| &result.track_censored_name.to_lowercase() == &self.name.to_lowercase())
&& (&result.artist_name.to_lowercase() == &self.artist.to_lowercase()
|| &result.collection_name.to_lowercase() == &self.album.to_lowercase())
});
match result {
Some(data) => {
self.artwork_url = Some(data.clone().artwork_url_100);
self.track_url = Some(data.clone().track_view_url);
}
None => (),
}
}
}
}
}
#[derive(Deserialize, Debug)]
pub struct Artwork {
pub class: String,
pub data: Option<String>,
pub description: Option<String>,
pub downloaded: bool,
pub format: Option<String>,
pub kind: i32,
pub raw_data: String,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct ITunesStoreSearch {
pub result_count: i32,
pub results: Vec<ITunesStoreData>,
}
#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
struct ITunesStoreData {
pub artist_name: String,
pub track_censored_name: String,
pub track_name: String,
pub artwork_url_100: String,
pub track_view_url: String,
pub collection_name: String,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum Kind {
User,
Computed,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum CloudStatus {
Unknown,
Purchased,
Matched,
Uploaded,
Ineligible,
Removed,
Error,
Duplicate,
Subscription,
Prerelease,
#[serde(rename = "no longer available")]
NoLongerAvailable,
NotUploaded,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub enum MediaKind {
Song,
#[serde(rename = "music video")]
MusicVideo,
Unknown,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub enum TrackKind {
SharedTrack,
FileTrack,
UrlTrack,
}