use aspotify::{ Album, Artist, Client, ClientCredentials, ItemType, Playlist, PlaylistItemType, Track, TrackSimplified, }; use librespot::core::authentication::Credentials; use librespot::core::cache::Cache; use librespot::core::config::SessionConfig; use librespot::core::session::Session; use std::fmt; use std::path::Path; use url::Url; use crate::error::SpotifyError; pub struct Spotify { // librespotify sessopm pub session: Session, pub spotify: Client, } impl Spotify { /// Create new instance pub async fn new( username: &str, password: &str, client_id: &str, client_secret: &str, ) -> Result { // librespot let credentials = Credentials::with_password(username, password); let session = Session::new( SessionConfig::default(), Some(Cache::new(Some(Path::new("credentials_cache")), None, None, None).unwrap()), ); session.connect(credentials, true) .await?; //aspotify let credentials = ClientCredentials { id: client_id.to_string(), secret: client_secret.to_string(), }; let spotify = Client::new(credentials); Ok(Spotify { session, spotify }) } /// Parse URI or URL into URI pub fn parse_uri(uri: &str) -> Result { // Already URI if uri.starts_with("spotify:") { if uri.split(':').count() < 3 { return Err(SpotifyError::InvalidUri); } return Ok(uri.to_string()); } // Parse URL let url = Url::parse(uri)?; // Spotify Web Player URL if url.host_str() == Some("open.spotify.com") { let path = url .path_segments() .ok_or_else(|| SpotifyError::Error("Missing URL path".into()))? .collect::>(); if path.len() < 2 { return Err(SpotifyError::InvalidUri); } return Ok(format!("spotify:{}:{}", path[0], path[1])); } Err(SpotifyError::InvalidUri) } /// Fetch data for URI pub async fn resolve_uri(&self, uri: &str) -> Result { let parts = uri.split(':').skip(1).collect::>(); let id = parts[1]; match parts[0] { "track" => { let track = self.spotify.tracks().get_track(id, None).await?; Ok(SpotifyItem::Track(track.data)) } "playlist" => { let playlist = self.spotify.playlists().get_playlist(id, None).await?; Ok(SpotifyItem::Playlist(playlist.data)) } "album" => { let album = self.spotify.albums().get_album(id, None).await?; Ok(SpotifyItem::Album(album.data)) } "artist" => { let artist = self.spotify.artists().get_artist(id).await?; Ok(SpotifyItem::Artist(artist.data)) } // Unsupported / Unimplemented _ => Ok(SpotifyItem::Other(uri.to_string())), } } /// Get search results for query pub async fn search(&self, query: &str) -> Result, SpotifyError> { Ok(self .spotify .search() .search(query, [ItemType::Track], true, 50, 0, None) .await? .data .tracks .unwrap() .items) } /// Get all tracks from playlist pub async fn full_playlist(&self, id: &str) -> Result, SpotifyError> { let mut items = vec![]; let mut offset = 0; loop { let page = self .spotify .playlists() .get_playlists_items(id, 100, offset, None) .await?; items.append( &mut page .data .items .iter() .filter_map(|i| -> Option { if let Some(PlaylistItemType::Track(t)) = &i.item { Some(t.to_owned()) } else { None } }) .collect(), ); // End offset += page.data.items.len(); if page.data.total == offset { return Ok(items); } } } /// Get all tracks from album pub async fn full_album(&self, id: &str) -> Result, SpotifyError> { let mut items = vec![]; let mut offset = 0; loop { let page = self .spotify .albums() .get_album_tracks(id, 50, offset, None) .await?; items.append(&mut page.data.items.to_vec()); // End offset += page.data.items.len(); if page.data.total == offset { return Ok(items); } } } /// Get all tracks from artist pub async fn full_artist(&self, id: &str) -> Result, SpotifyError> { let mut items = vec![]; let mut offset = 0; loop { let page = self .spotify .artists() .get_artist_albums(id, None, 50, offset, None) .await?; for album in &mut page.data.items.iter() { items.append(&mut self.full_album(&album.id).await?) } // End offset += page.data.items.len(); if page.data.total == offset { return Ok(items); } } } } impl Clone for Spotify { fn clone(&self) -> Self { Self { session: self.session.clone(), spotify: Client::new(self.spotify.credentials.clone()), } } } /// Basic debug implementation so can be used in other structs impl fmt::Debug for Spotify { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "") } } #[derive(Debug, Clone)] pub enum SpotifyItem { Track(Track), Album(Album), Playlist(Playlist), Artist(Artist), /// Unimplemented Other(String), }