From d069ae058f8192d5069e4af1121bd526fa351288 Mon Sep 17 00:00:00 2001 From: Davide Polonio Date: Fri, 11 Mar 2022 14:31:43 +0100 Subject: [PATCH] chore: bump deps versions, switch to rspotify lib --- Cargo.toml | 10 ++-- Dockerfile | 2 +- src/main.rs | 32 ++++++++-- src/spotify/mod.rs | 145 +++++++++++++++++++++++++++++++-------------- 4 files changed, 136 insertions(+), 53 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 673ed0c..ac7c90b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "songlify" -version = "0.2.2" +version = "0.2.3" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -teloxide = { version = "0.5", features = ["auto-send", "macros"] } -log = "0.4" +teloxide = { version = "0.7.1", features = ["auto-send", "macros"] } +log = "0.4.14" pretty_env_logger = "0.4.0" -tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] } -aspotify = "0.7.0" +tokio = { version = "1.17.0", features = ["rt-multi-thread", "macros"] } +rspotify = { version = "0.11.4", features = ["default"]} diff --git a/Dockerfile b/Dockerfile index aff8dea..e632ae8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.56.1-slim-bullseye as builder +FROM rust:1.59.0-slim-bullseye as builder WORKDIR /build diff --git a/src/main.rs b/src/main.rs index 8cb3183..fc8f0af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ use teloxide::prelude::*; -use crate::spotify::TrackInfo; +use crate::spotify::{PlayableKind, TrackInfo}; use spotify::SpotifyKind::Track; -use crate::spotify::SpotifyKind::{Album, Playlist}; +use crate::spotify::SpotifyKind::{Album, Episode, Playlist, Podcast}; use crate::utils::{human_readable_duration, truncate_with_dots}; mod spotify; @@ -21,7 +21,7 @@ async fn main() { let text = message.update.text().and_then(spotify::get_entry_kind); match text { Some(spotify) => { - let spotify_client = spotify::get_client(); + let spotify_client = spotify::get_client().await; match spotify { Track(id) => { log::debug!("Parsing spotify song: {}", id); @@ -85,12 +85,20 @@ async fn main() { ); Some( message - .reply_to(add_track_section(info.tracks, reply)) + .reply_to(add_track_section_for_playlist(info.tracks, reply)) .await?, ) } None => None, } + }, + Episode(id) => { + log::warn!("Support for episodes ({}) has not be implemented yet!", id); + None + } + Podcast(id) => { + log::warn!("Support for podcasts ({}) has not be implemented yet!", id); + None } } } @@ -103,6 +111,22 @@ async fn main() { log::info!("Exiting..."); } +fn add_track_section_for_playlist(tracks: Vec, reply: String) -> String { + if !tracks.is_empty() { + let songs = tracks + .iter() + .map(|x| match x { + PlayableKind::Track(t) => t.name.clone(), + PlayableKind::Podcast(e) => e.name.clone() + } + "\n") + .collect::(); + reply + .clone() + .push_str(format!("\n🎶 {} Track(s): {}", tracks.len(), songs).as_str()) + } + reply +} + fn add_track_section(tracks: Vec, reply: String) -> String { if !tracks.is_empty() { let songs = tracks diff --git a/src/spotify/mod.rs b/src/spotify/mod.rs index 0606aa9..3b9bb8b 100644 --- a/src/spotify/mod.rs +++ b/src/spotify/mod.rs @@ -1,12 +1,20 @@ use std::time::Duration; - -use aspotify::PlaylistItemType::{Episode, Track}; -use aspotify::{Client, ClientCredentials, TrackSimplified}; +use rspotify::{ClientCredsSpotify, Credentials}; +use rspotify::model::{AlbumId, FullTrack, PlaylistId, TrackId}; +use rspotify::model::PlayableItem::{Episode, Track}; +use rspotify::prelude::*; pub enum SpotifyKind { Track(String), Album(String), Playlist(String), + Podcast(String), + Episode(String) +} + +pub enum PlayableKind { + Track(TrackInfo), + Podcast(EpisodeInfo) } pub struct TrackInfo { @@ -15,6 +23,15 @@ pub struct TrackInfo { pub(crate) duration: Duration, } +pub struct EpisodeInfo { + pub(crate) name: String, + pub(crate) show: String, + pub(crate) duration: Duration, + pub(crate) description: String, + pub(crate) languages: Vec, + pub(crate) release_date: String +} + pub struct AlbumInfo { pub(crate) name: String, pub(crate) artists: Vec, @@ -25,7 +42,7 @@ pub struct AlbumInfo { pub struct PlaylistInfo { pub(crate) name: String, pub(crate) artists: Vec, - pub(crate) tracks: Vec, + pub(crate) tracks: Vec, } fn get_id_in_url(url: &str) -> Option<&str> { @@ -35,7 +52,7 @@ fn get_id_in_url(url: &str) -> Option<&str> { .and_then(|x| x.split('?').next()) } -fn extract_artists_from_tracks(tracks: Vec) -> Vec { +fn extract_artists_from_tracks(tracks: Vec) -> Vec { tracks .iter() .flat_map(|t| t.artists.iter().map(|a| a.name.clone())) @@ -64,35 +81,58 @@ pub fn get_entry_kind(url: &str) -> Option { None => None, }; } + if url.contains("https://open.spotify.com/show/") { + let playlist_id = get_id_in_url(url); + return match playlist_id { + Some(id) => Some(SpotifyKind::Podcast(id.to_string())), + None => None, + }; + } + if url.contains("https://open.spotify.com/episode/") { + let playlist_id = get_id_in_url(url); + return match playlist_id { + Some(id) => Some(SpotifyKind::Episode(id.to_string())), + None => None, + }; + } return None; } -pub fn get_client() -> Box { - let spotify_creds = - ClientCredentials::from_env().expect("CLIENT_ID and CLIENT_SECRET not found."); - let spotify_client = Box::new(Client::new(spotify_creds)); - spotify_client +pub async fn get_client() -> Box { + let spotify_creds = Credentials::from_env().expect("RSPOTIFY_CLIENT_ID and RSPOTIFY_CLIENT_SECRET not found."); + let mut spotify = ClientCredsSpotify::new(spotify_creds); + spotify.request_token().await.unwrap(); + Box::new(spotify) } -pub async fn get_track(spotify: Box, id: &String) -> Option { - match spotify.tracks().get_track(id.as_str(), None).await { - Ok(track) => Some(TrackInfo { - name: track.data.name, - artists: track.data.artists.iter().map(|x| x.name.clone()).collect(), - duration: track.data.duration, +pub async fn get_track(spotify: Box, id: &String) -> Option { + let track_id = match TrackId::from_id(id.as_str()) { + Ok(track) => track, + Err(_e) => return None + }; + + match spotify.track(&track_id).await { + Ok(track) => Some(TrackInfo{ + name: track.name, + artists: track.artists.iter().map(|x| x.name.clone()).collect(), + duration: track.duration, }), - Err(_e) => None, + Err(_e) => None } } -pub async fn get_album(spotify: Box, id: &String) -> Option { - match spotify.albums().get_album(id.as_str(), None).await { +pub async fn get_album(spotify: Box, id: &String) -> Option { + let album_id = match AlbumId::from_id(id.as_str()) { + Ok(album) => album, + Err(_e) => return None + }; + + match spotify.album(&album_id).await { Ok(album) => Some(AlbumInfo { - name: album.data.name, - artists: album.data.artists.iter().map(|x| x.name.clone()).collect(), - genres: album.data.genres, + name: album.name, + artists: album.artists.iter().map(|x| x.name.clone()).collect(), + genres: album.genres, tracks: album - .data .tracks .items .iter() @@ -107,37 +147,56 @@ pub async fn get_album(spotify: Box, id: &String) -> Option { } } -pub async fn get_playlist(spotify: Box, id: &String) -> Option { - match spotify.playlists().get_playlist(id.as_str(), None).await { +pub async fn get_playlist(spotify: Box, id: &String) -> Option { + let playlist_id = match PlaylistId::from_id(id.as_str()) { + Ok(playlist) => playlist, + Err(_e) => return None + }; + + match spotify.playlist(&playlist_id, None, None).await { Ok(playlist) => Some(PlaylistInfo { - name: playlist.data.name, + name: playlist.name, artists: playlist - .data .tracks .items .iter() .flat_map(|p| { - p.item.as_ref().map_or(Vec::new(), |x| match x { - Track(t) => extract_artists_from_tracks(vec![t.clone().simplify()]), - Episode(_e) => Vec::new(), - }) - }) - .collect(), + match &p.track { + Some(t) => match t { + Track(t) => t.artists.iter().map(|a| { + a.name.clone() + }).collect(), + Episode(e) => vec![e.show.publisher.clone()] + } + None => Vec::new(), + }.into_iter() + }).collect(), tracks: playlist - .data .tracks .items .iter() - .flat_map(|p| { - p.item.as_ref().map_or(None, |x| match x { - Track(t) => Some(TrackInfo { - name: t.name.clone(), - artists: extract_artists_from_tracks(vec![t.clone().simplify()]), - duration: t.duration, - }), - Episode(_e) => None, - }) + .map(|p| { + match &p.track { + Some(t) => match t { + Track(t) => Some(PlayableKind::Track(TrackInfo{ + name: t.name.clone(), + artists: t.artists.iter().map(|a| a.name.clone()).collect(), + duration: t.duration + })), + Episode(e) => Some(PlayableKind::Podcast(EpisodeInfo{ + name: e.name.clone(), + show: e.show.name.clone(), + duration: e.duration, + description: e.description.clone(), + languages: e.languages.clone(), + release_date: e.release_date.clone() + })) + }, + None => None + } }) + .filter(|i| i.is_some()) + .map(|i| i.unwrap()) .collect(), }), Err(_e) => None,