From cea5958e43e92c089096667bcb414bbacc5f24d4 Mon Sep 17 00:00:00 2001 From: Davide Polonio Date: Tue, 28 Sep 2021 15:20:28 +0200 Subject: [PATCH 1/6] feat: add support for album links - perform little code refactor, create new module spotify --- Cargo.toml | 2 +- src/main.rs | 77 ++++++++++++++++++++++++---------------------- src/spotify/mod.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 38 deletions(-) create mode 100644 src/spotify/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 37f1f2b..7a80f6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "songlify" -version = "0.1.0" +version = "0.2.0" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/main.rs b/src/main.rs index 492b240..501a043 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,39 +1,13 @@ -use crate::SpotifyURL::Track; -use aspotify::{Client, ClientCredentials}; use std::time::Duration; + +use aspotify::Client; use teloxide::prelude::*; -enum SpotifyURL { - Track(String), -} +use spotify::SpotifyKind::Track; -struct TrackInfo { - name: String, - artist: Vec, - duration: Duration, -} +use crate::spotify::SpotifyKind::Album; -fn get_spotify_entry(url: &str) -> Option { - if url.contains("https://open.spotify.com/track/") { - let track_id = url.rsplit('/').next().and_then(|x| x.split('?').next()); - return match track_id { - Some(id) => Some(SpotifyURL::Track(id.to_string())), - None => None, - }; - } - return None; -} - -async fn get_spotify_track(spotify: Box, id: &String) -> Option { - match spotify.tracks().get_track(id.as_str(), None).await { - Ok(track) => Some(TrackInfo { - name: track.data.name, - artist: track.data.artists.iter().map(|x| x.name.clone()).collect(), - duration: track.data.duration, - }), - Err(_e) => None, - } -} +mod spotify; #[tokio::main] async fn main() { @@ -42,16 +16,14 @@ async fn main() { let bot = Bot::from_env().auto_send(); teloxide::repl(bot, |message| async move { - let text = message.update.text().and_then(get_spotify_entry); + let text = message.update.text().and_then(spotify::get_spotify_entry); match text { Some(spotify) => { - let spotify_creds = - ClientCredentials::from_env().expect("CLIENT_ID and CLIENT_SECRET not found."); - let spotify_client = Box::new(Client::new(spotify_creds)); + let spotify_client = spotify::get_spotify_client(); match spotify { Track(id) => { log::debug!("Parsing spotify song: {}", id); - let track_info = get_spotify_track(spotify_client, &id).await; + let track_info = spotify::get_spotify_track(spotify_client, &id).await; match track_info { Some(info) => { let reply = format!( @@ -60,7 +32,7 @@ async fn main() { šŸ§‘ā€šŸŽ¤ Artist(s): {}\n\ ā³ Duration: {} second(s)", info.name, - info.artist.join(", "), + info.artists.join(", "), info.duration.as_secs() ); Some(message.reply_to(reply).await?) @@ -68,6 +40,37 @@ async fn main() { None => None, } } + Album(id) => { + log::debug!("Parsing spotify album: {}", id); + let album_info = spotify::get_spotify_album(spotify_client, &id).await; + match album_info { + Some(info) => { + let mut reply = format!( + "Album information:\n\ + šŸŽµ Album name name: {}\n\ + šŸ§‘ā€šŸŽ¤ Artist(s): {}", + info.name, + info.artists.join(", ") + ); + if !info.genres.is_empty() { + reply.push_str( + format!("\nšŸ’æ Genre(s): {}", info.genres.join(", ")) + .as_str(), + ); + } + if !info.songs.is_empty() { + let songs = info + .songs + .iter() + .map(|x| x.name.clone() + "\n") + .collect::(); + reply.push_str(format!("\nšŸŽ¶ Song(s): {}", songs).as_str()); + } + Some(message.reply_to(reply).await?) + } + None => None, + } + } } } None => None, diff --git a/src/spotify/mod.rs b/src/spotify/mod.rs new file mode 100644 index 0000000..e56f36b --- /dev/null +++ b/src/spotify/mod.rs @@ -0,0 +1,76 @@ +use std::time::Duration; + +use aspotify::{Client, ClientCredentials}; + +pub enum SpotifyKind { + Track(String), + Album(String), +} + +fn get_id_in_url(url: &str) -> Option<&str> { + url.rsplit('/') + .next() + .and_then(|x| x.split(' ').next()) + .and_then(|x| x.split('?').next()) +} + +pub fn get_spotify_entry(url: &str) -> Option { + if url.contains("https://open.spotify.com/track/") { + let track_id = get_id_in_url(url); + return match track_id { + Some(id) => Some(SpotifyKind::Track(id.to_string())), + None => None, + }; + } + if url.contains("https://open.spotify.com/album/") { + let album_id = get_id_in_url(url); + return match album_id { + Some(id) => Some(SpotifyKind::Album(id.to_string())), + None => None, + }; + } + return None; +} + +pub fn get_spotify_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 +} + +struct TrackInfo { + name: String, + artists: Vec, + duration: Duration, +} + +struct AlbumInfo { + pub(crate) name: String, + pub(crate) artists: Vec, + pub(crate) genres: Vec, + songs: Vec, +} + +pub async fn get_spotify_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, + }), + Err(_e) => None, + } +} + +pub async fn get_spotify_album(spotify: Box, id: &String) -> Option { + match spotify.albums().get_album(id.as_str(), None).await { + Ok(album) => Some(AlbumInfo { + name: album.data.name, + artists: album.data.artists.iter().map(|x| x.name.clone()).collect(), + genres: album.data.genres, + songs: vec![], // TODO we could lookup songs and put them here! + }), + Err(_e) => None, + } +} -- 2.40.1 From 63eb891f91312d5d7c0470fded76c0a2c3d5b403 Mon Sep 17 00:00:00 2001 From: Davide Polonio Date: Tue, 28 Sep 2021 15:29:31 +0200 Subject: [PATCH 2/6] fix: set right visibility in structs & fields - Cleanup old imports --- src/main.rs | 3 --- src/spotify/mod.rs | 12 ++++++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index 501a043..b5e2379 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,3 @@ -use std::time::Duration; - -use aspotify::Client; use teloxide::prelude::*; use spotify::SpotifyKind::Track; diff --git a/src/spotify/mod.rs b/src/spotify/mod.rs index e56f36b..cb54cb6 100644 --- a/src/spotify/mod.rs +++ b/src/spotify/mod.rs @@ -39,17 +39,17 @@ pub fn get_spotify_client() -> Box { spotify_client } -struct TrackInfo { - name: String, - artists: Vec, - duration: Duration, +pub struct TrackInfo { + pub(crate) name: String, + pub(crate) artists: Vec, + pub(crate) duration: Duration, } -struct AlbumInfo { +pub struct AlbumInfo { pub(crate) name: String, pub(crate) artists: Vec, pub(crate) genres: Vec, - songs: Vec, + pub(crate) songs: Vec, } pub async fn get_spotify_track(spotify: Box, id: &String) -> Option { -- 2.40.1 From 1cda3984d8ff6f407365c3b8e105da60f90962e9 Mon Sep 17 00:00:00 2001 From: Davide Polonio Date: Wed, 10 Nov 2021 14:31:42 +0100 Subject: [PATCH 3/6] feat(spotify): add playlist support, add pre-commit checks --- .dockerignore | 2 +- .gitignore | 1 - .idea/codeStyles/Project.xml | 11 ++++ .idea/codeStyles/codeStyleConfig.xml | 5 ++ .idea/google-java-format.xml | 6 +++ .idea/misc.xml | 5 +- .idea/modules.xml | 2 +- .idea/runConfigurations.xml | 2 +- .idea/sbt.xml | 6 +++ .idea/vcs.xml | 2 +- .pre-commit-config.yaml | 17 +++++++ Cargo.toml | 4 +- Dockerfile | 2 +- README.md | 2 +- songlify.iml | 2 +- src/main.rs | 58 ++++++++++++++++----- src/spotify/mod.rs | 75 ++++++++++++++++++++++++++-- 17 files changed, 173 insertions(+), 29 deletions(-) create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/google-java-format.xml create mode 100644 .idea/sbt.xml create mode 100644 .pre-commit-config.yaml diff --git a/.dockerignore b/.dockerignore index 07827cc..96ef862 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,2 @@ target/ -.idea/ \ No newline at end of file +.idea/ diff --git a/.gitignore b/.gitignore index 62bd1a4..70b1c1c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,3 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk - diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..37c5bdd --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..1a0176a --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + diff --git a/.idea/google-java-format.xml b/.idea/google-java-format.xml new file mode 100644 index 0000000..7c69eed --- /dev/null +++ b/.idea/google-java-format.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/.idea/misc.xml b/.idea/misc.xml index bfecbb1..9360735 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,7 +3,4 @@ - - - \ No newline at end of file + diff --git a/.idea/modules.xml b/.idea/modules.xml index 68e6503..c56df5b 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -5,4 +5,4 @@ - \ No newline at end of file + diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml index 797acea..a1fe99e 100644 --- a/.idea/runConfigurations.xml +++ b/.idea/runConfigurations.xml @@ -7,4 +7,4 @@ - \ No newline at end of file + diff --git a/.idea/sbt.xml b/.idea/sbt.xml new file mode 100644 index 0000000..a6f46a0 --- /dev/null +++ b/.idea/sbt.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 35eb1dd..dcb6b8c 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -3,4 +3,4 @@ - \ No newline at end of file + diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..683049a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,17 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: check-merge-conflict + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files +- repo: https://github.com/doublify/pre-commit-rust + rev: v1.0 + hooks: + - id: fmt + args: ['--verbose', '--'] + - id: cargo-check diff --git a/Cargo.toml b/Cargo.toml index 7a80f6f..60d374c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "songlify" -version = "0.2.0" +version = "0.2.1" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -10,4 +10,4 @@ teloxide = { version = "0.5", features = ["auto-send", "macros"] } log = "0.4" pretty_env_logger = "0.4.0" tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] } -aspotify = "0.7.0" \ No newline at end of file +aspotify = "0.7.0" diff --git a/Dockerfile b/Dockerfile index 0dbde9c..c42db36 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,4 +15,4 @@ COPY --from=builder /build/target/release/songlify /usr/bin/songlify COPY --from=builder /lib/x86_64-linux-gnu/libgcc_s.so.1 /lib/x86_64-linux-gnu/ COPY --from=builder /usr/lib/x86_64-linux-gnu/libstdc++.so.6 /usr/lib/x86_64-linux-gnu/ -ENTRYPOINT /usr/bin/songlify \ No newline at end of file +ENTRYPOINT /usr/bin/songlify diff --git a/README.md b/README.md index b4b27b7..17eeeed 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # Songlify -A Telegram bot that gives you information about the song other people share with you in chat. \ No newline at end of file +A Telegram bot that gives you information about the song other people share with you in chat. diff --git a/songlify.iml b/songlify.iml index 2fecef3..601d455 100644 --- a/songlify.iml +++ b/songlify.iml @@ -9,4 +9,4 @@ - \ No newline at end of file + diff --git a/src/main.rs b/src/main.rs index b5e2379..c1ff728 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ use teloxide::prelude::*; +use crate::spotify::TrackInfo; use spotify::SpotifyKind::Track; -use crate::spotify::SpotifyKind::Album; +use crate::spotify::SpotifyKind::{Album, Playlist}; mod spotify; @@ -44,9 +45,10 @@ async fn main() { Some(info) => { let mut reply = format!( "Album information:\n\ - šŸŽµ Album name name: {}\n\ - šŸ§‘ā€šŸŽ¤ Artist(s): {}", + šŸŽµ Album name: {}\n\ + šŸ§‘ā€šŸŽ¤ {} artist(s): {}", info.name, + info.artists.len(), info.artists.join(", ") ); if !info.genres.is_empty() { @@ -55,15 +57,34 @@ async fn main() { .as_str(), ); } - if !info.songs.is_empty() { - let songs = info - .songs - .iter() - .map(|x| x.name.clone() + "\n") - .collect::(); - reply.push_str(format!("\nšŸŽ¶ Song(s): {}", songs).as_str()); - } - Some(message.reply_to(reply).await?) + Some( + message + .reply_to(add_track_section(info.tracks, reply)) + .await?, + ) + } + None => None, + } + } + Playlist(id) => { + log::debug!("Parsing spotify playlist: {}", id); + let playlist_info = + spotify::get_spotify_playlist(spotify_client, &id).await; + match playlist_info { + Some(info) => { + let reply = format!( + "Playlist information:\n\ + āœ’ļø Playlist name: {}\n\ + šŸ§‘ā€šŸŽ¤ {} artist(s): {}", + info.name, + info.artists.len(), + info.artists.join(", ") + ); + Some( + message + .reply_to(add_track_section(info.tracks, reply)) + .await?, + ) } None => None, } @@ -78,3 +99,16 @@ async fn main() { log::info!("Exiting..."); } + +fn add_track_section(tracks: Vec, reply: String) -> String { + if !tracks.is_empty() { + let songs = tracks + .iter() + .map(|x| x.name.clone() + "\n") + .collect::(); + reply + .clone() + .push_str(format!("\nšŸŽ¶ {} Track(s): {}", tracks.len(), songs).as_str()) + } + reply +} diff --git a/src/spotify/mod.rs b/src/spotify/mod.rs index cb54cb6..d77ef4c 100644 --- a/src/spotify/mod.rs +++ b/src/spotify/mod.rs @@ -1,10 +1,12 @@ use std::time::Duration; -use aspotify::{Client, ClientCredentials}; +use aspotify::PlaylistItemType::{Episode, Track}; +use aspotify::{Client, ClientCredentials, TrackSimplified}; pub enum SpotifyKind { Track(String), Album(String), + Playlist(String), } fn get_id_in_url(url: &str) -> Option<&str> { @@ -29,6 +31,13 @@ pub fn get_spotify_entry(url: &str) -> Option { None => None, }; } + if url.contains("https://open.spotify.com/playlist/") { + let playlist_id = get_id_in_url(url); + return match playlist_id { + Some(id) => Some(SpotifyKind::Playlist(id.to_string())), + None => None, + }; + } return None; } @@ -49,7 +58,13 @@ pub struct AlbumInfo { pub(crate) name: String, pub(crate) artists: Vec, pub(crate) genres: Vec, - pub(crate) songs: Vec, + pub(crate) tracks: Vec, +} + +pub struct PlaylistInfo { + pub(crate) name: String, + pub(crate) artists: Vec, + pub(crate) tracks: Vec, } pub async fn get_spotify_track(spotify: Box, id: &String) -> Option { @@ -69,7 +84,61 @@ pub async fn get_spotify_album(spotify: Box, id: &String) -> Option None, + } +} + +fn extract_artists_from_tracks(tracks: Vec) -> Vec { + tracks + .iter() + .flat_map(|t| t.artists.iter().map(|a| a.name.clone())) + .collect() +} + +pub async fn get_spotify_playlist(spotify: Box, id: &String) -> Option { + match spotify.playlists().get_playlist(id.as_str(), None).await { + Ok(playlist) => Some(PlaylistInfo { + name: playlist.data.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(), + 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, + }) + }) + .collect(), }), Err(_e) => None, } -- 2.40.1 From c2307b53a7576f1103acc61b90c3d8b5399d1268 Mon Sep 17 00:00:00 2001 From: Davide Polonio Date: Wed, 10 Nov 2021 14:35:02 +0100 Subject: [PATCH 4/6] chore(spotify): refactor spotify wrapper & function names --- src/main.rs | 11 ++++---- src/spotify/mod.rs | 62 +++++++++++++++++++++++----------------------- 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/main.rs b/src/main.rs index c1ff728..d1b3186 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,14 +14,14 @@ async fn main() { let bot = Bot::from_env().auto_send(); teloxide::repl(bot, |message| async move { - let text = message.update.text().and_then(spotify::get_spotify_entry); + let text = message.update.text().and_then(spotify::get_entry_kind); match text { Some(spotify) => { - let spotify_client = spotify::get_spotify_client(); + let spotify_client = spotify::get_client(); match spotify { Track(id) => { log::debug!("Parsing spotify song: {}", id); - let track_info = spotify::get_spotify_track(spotify_client, &id).await; + let track_info = spotify::get_track(spotify_client, &id).await; match track_info { Some(info) => { let reply = format!( @@ -40,7 +40,7 @@ async fn main() { } Album(id) => { log::debug!("Parsing spotify album: {}", id); - let album_info = spotify::get_spotify_album(spotify_client, &id).await; + let album_info = spotify::get_album(spotify_client, &id).await; match album_info { Some(info) => { let mut reply = format!( @@ -68,8 +68,7 @@ async fn main() { } Playlist(id) => { log::debug!("Parsing spotify playlist: {}", id); - let playlist_info = - spotify::get_spotify_playlist(spotify_client, &id).await; + let playlist_info = spotify::get_playlist(spotify_client, &id).await; match playlist_info { Some(info) => { let reply = format!( diff --git a/src/spotify/mod.rs b/src/spotify/mod.rs index d77ef4c..0606aa9 100644 --- a/src/spotify/mod.rs +++ b/src/spotify/mod.rs @@ -9,6 +9,25 @@ pub enum SpotifyKind { Playlist(String), } +pub struct TrackInfo { + pub(crate) name: String, + pub(crate) artists: Vec, + pub(crate) duration: Duration, +} + +pub struct AlbumInfo { + pub(crate) name: String, + pub(crate) artists: Vec, + pub(crate) genres: Vec, + pub(crate) tracks: Vec, +} + +pub struct PlaylistInfo { + pub(crate) name: String, + pub(crate) artists: Vec, + pub(crate) tracks: Vec, +} + fn get_id_in_url(url: &str) -> Option<&str> { url.rsplit('/') .next() @@ -16,7 +35,14 @@ fn get_id_in_url(url: &str) -> Option<&str> { .and_then(|x| x.split('?').next()) } -pub fn get_spotify_entry(url: &str) -> Option { +fn extract_artists_from_tracks(tracks: Vec) -> Vec { + tracks + .iter() + .flat_map(|t| t.artists.iter().map(|a| a.name.clone())) + .collect() +} + +pub fn get_entry_kind(url: &str) -> Option { if url.contains("https://open.spotify.com/track/") { let track_id = get_id_in_url(url); return match track_id { @@ -41,33 +67,14 @@ pub fn get_spotify_entry(url: &str) -> Option { return None; } -pub fn get_spotify_client() -> Box { +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 struct TrackInfo { - pub(crate) name: String, - pub(crate) artists: Vec, - pub(crate) duration: Duration, -} - -pub struct AlbumInfo { - pub(crate) name: String, - pub(crate) artists: Vec, - pub(crate) genres: Vec, - pub(crate) tracks: Vec, -} - -pub struct PlaylistInfo { - pub(crate) name: String, - pub(crate) artists: Vec, - pub(crate) tracks: Vec, -} - -pub async fn get_spotify_track(spotify: Box, id: &String) -> Option { +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, @@ -78,7 +85,7 @@ pub async fn get_spotify_track(spotify: Box, id: &String) -> Option, id: &String) -> Option { +pub async fn get_album(spotify: Box, id: &String) -> Option { match spotify.albums().get_album(id.as_str(), None).await { Ok(album) => Some(AlbumInfo { name: album.data.name, @@ -100,14 +107,7 @@ pub async fn get_spotify_album(spotify: Box, id: &String) -> Option) -> Vec { - tracks - .iter() - .flat_map(|t| t.artists.iter().map(|a| a.name.clone())) - .collect() -} - -pub async fn get_spotify_playlist(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 { Ok(playlist) => Some(PlaylistInfo { name: playlist.data.name, -- 2.40.1 From 46edd0f78107c0108eb730837effe7aa1968b8ad Mon Sep 17 00:00:00 2001 From: Davide Polonio Date: Wed, 10 Nov 2021 14:59:57 +0100 Subject: [PATCH 5/6] chore(docker): upgrade Docker image and lint it --- .pre-commit-config.yaml | 4 ++++ Dockerfile | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 683049a..c0be7d5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,3 +15,7 @@ repos: - id: fmt args: ['--verbose', '--'] - id: cargo-check +- repo: https://github.com/IamTheFij/docker-pre-commit + rev: v2.0.1 + hooks: + - id: hadolint diff --git a/Dockerfile b/Dockerfile index c42db36..aff8dea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,18 @@ -FROM rust:1.55.0-bullseye as builder +FROM rust:1.56.1-slim-bullseye as builder -# ENV RUSTFLAGS="-C target-feature=+crt-static" WORKDIR /build -RUN apt-get update && apt-get install -y \ - libssl-dev +RUN apt-get update && apt-get install -y --no-install-recommends \ + libssl-dev=1.1.1k-1+deb11u1 \ + pkg-config=0.29.2-1 COPY ./ /build RUN cargo build --release -FROM gcr.io/distroless/base +FROM gcr.io/distroless/base:latest-amd64 COPY --from=builder /build/target/release/songlify /usr/bin/songlify COPY --from=builder /lib/x86_64-linux-gnu/libgcc_s.so.1 /lib/x86_64-linux-gnu/ COPY --from=builder /usr/lib/x86_64-linux-gnu/libstdc++.so.6 /usr/lib/x86_64-linux-gnu/ -ENTRYPOINT /usr/bin/songlify +ENTRYPOINT ["/usr/bin/songlify"] -- 2.40.1 From 7c828a55db1eb346fe3110cde4a5a94c29da4e6e Mon Sep 17 00:00:00 2001 From: Davide Polonio Date: Sat, 13 Nov 2021 16:30:17 +0100 Subject: [PATCH 6/6] feat: improve formatting output * duration is now in human readable format * list of artists is now capped at 140 chars max --- Cargo.toml | 2 +- src/main.rs | 14 ++++--- src/utils/mod.rs | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 src/utils/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 60d374c..673ed0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "songlify" -version = "0.2.1" +version = "0.2.2" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/main.rs b/src/main.rs index d1b3186..8cb3183 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,12 @@ use crate::spotify::TrackInfo; use spotify::SpotifyKind::Track; use crate::spotify::SpotifyKind::{Album, Playlist}; +use crate::utils::{human_readable_duration, truncate_with_dots}; mod spotify; +mod utils; + +static MAX_ARTISTS_CHARS: usize = 140; #[tokio::main] async fn main() { @@ -28,10 +32,10 @@ async fn main() { "Track information:\n\ šŸŽµ Track name: {}\n\ šŸ§‘ā€šŸŽ¤ Artist(s): {}\n\ - ā³ Duration: {} second(s)", + ā³ Duration: {}", info.name, - info.artists.join(", "), - info.duration.as_secs() + truncate_with_dots(info.artists.join(", "), MAX_ARTISTS_CHARS), + human_readable_duration(info.duration) ); Some(message.reply_to(reply).await?) } @@ -49,7 +53,7 @@ async fn main() { šŸ§‘ā€šŸŽ¤ {} artist(s): {}", info.name, info.artists.len(), - info.artists.join(", ") + truncate_with_dots(info.artists.join(", "), MAX_ARTISTS_CHARS) ); if !info.genres.is_empty() { reply.push_str( @@ -77,7 +81,7 @@ async fn main() { šŸ§‘ā€šŸŽ¤ {} artist(s): {}", info.name, info.artists.len(), - info.artists.join(", ") + truncate_with_dots(info.artists.join(", "), MAX_ARTISTS_CHARS) ); Some( message diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..3b4c338 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,96 @@ +use std::ops::Add; +use std::time::Duration; + +pub(crate) fn truncate_with_dots(to_truncate: String, new_size: usize) -> String { + if to_truncate.len() < new_size { + return to_truncate; + } + + let mut new_string = to_truncate.clone(); + let dots = "..."; + if new_size as isize - 3 > 0 { + new_string.truncate(new_size - 3); + new_string.add(dots) + } else { + let mut dots_to_ret = String::new(); + for _i in 0..new_size { + dots_to_ret.push('.'); + } + return dots_to_ret; + } +} + +pub(crate) fn human_readable_duration(duration: Duration) -> String { + let total_duration = duration.as_secs(); + let mut minutes = total_duration / 60; + let seconds = total_duration % 60; + + if minutes >= 60 { + let hours = minutes / 60; + minutes = minutes % 60; + return format!( + "{} hour(s), {} minute(s) and {} second(s)", + hours, minutes, seconds + ); + } + format!("{} minute(s) and {} second(s)", minutes, seconds) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn should_truncate_short_string_as_expected() { + let example_string = "example"; + let expected_string = ".."; + + assert_eq!( + expected_string, + truncate_with_dots(example_string.to_string(), 2) + ) + } + + #[test] + fn should_truncate_long_string_as_expected() { + let example_string = "this is a very long string"; + let expected_string = "this i..."; + + assert_eq!( + expected_string, + truncate_with_dots(example_string.to_string(), 9) + ) + } + + #[test] + fn should_not_truncate_if_string_is_not_long_enough() { + let example_string = "short string"; + let expected_string = "short string"; + + assert_eq!( + expected_string, + truncate_with_dots(example_string.to_string(), 1000) + ); + } + + #[test] + fn should_print_correct_duration_into_human_readable_format() { + let duration: Duration = Duration::new(124, 0); + let got = human_readable_duration(duration); + + assert_eq!("2 minute(s) and 4 second(s)", got) + } + + #[test] + fn should_handle_duration_in_hours() { + let duration1 = Duration::new(3621, 0); + let got1 = human_readable_duration(duration1); + + assert_eq!("1 hour(s), 0 minute(s) and 21 second(s)", got1); + + let duration2 = Duration::new(5021, 0); + let got2 = human_readable_duration(duration2); + + assert_eq!("1 hour(s), 23 minute(s) and 41 second(s)", got2); + } +} -- 2.40.1