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, }