feat: add first engine song search

pull/8/head
Davide Polonio 2022-06-08 18:17:22 +02:00
parent 06fff8e972
commit ba2b689e77
3 changed files with 78 additions and 90 deletions

View File

@ -1,37 +1,15 @@
use crate::spotify::ContentKind;
use crate::{spotify, youtube};
use chrono::{Date, Utc};
use std::time::Duration;
pub(crate) enum MusicData {
Track(Track),
Album(Album),
}
use crate::youtube::Video;
use crate::{spotify, youtube, TrackInfo};
#[derive(Debug, Clone)]
pub(crate) struct Track {
name: String,
authors: Vec<Author>,
duration: Duration,
album: Option<Album>,
description: Option<String>,
lyrics: Option<String>,
}
#[derive(Debug, Clone)]
pub(crate) struct Album {
name: String,
authors: Vec<Author>,
description: Option<String>,
year: Option<Date<Utc>>,
}
#[derive(Debug, Clone)]
pub(crate) struct Author {
name: String,
pub(crate) struct TrackItem {
spotify_track: Option<TrackInfo>,
youtube_track: Option<Vec<Video>>,
}
// The enum holds all the currently supported type of Id which the engine can search for
#[derive(Debug, Clone)]
pub(crate) enum ServiceIdKind {
Spotify(String),
Youtube(String),
@ -40,6 +18,7 @@ pub(crate) enum ServiceIdKind {
// This struct will allow us in the future to search, cache and store data and metadata regarding
// tracks, albums and playlists
#[derive(Debug, Clone)]
pub(crate) struct MusicEngine {
spotify: spotify::Client,
youtube: youtube::Client,
@ -63,56 +42,57 @@ impl MusicEngine {
}
}
pub(crate) async fn search_by_name(&self, name: &str) {
pub(crate) async fn search_song_by_name(&self, name: &str) {
todo!("In the future it would be possible to search for all metadata on a record from this call")
}
pub(crate) async fn search_by_id(&self, id: ServiceIdKind) -> Option<MusicData> {
match id {
ServiceIdKind::Spotify(id) => {
let entry_kind = spotify::get_entry_kind(id.as_str());
match entry_kind {
None => None,
pub(crate) async fn search_song_by_id(&self, id: &str) -> Option<TrackItem> {
let entry_kind = spotify::get_entry_kind(id);
let track_info = match entry_kind {
Some(entry) => match entry {
ContentKind::Track(id) => {
self.spotify.get_track(id.as_str()).await.map(|track| {
MusicData::Track(Track {
name: track.name.clone(),
authors: track
.artists
.iter()
.map(|artist| Author {
name: artist.clone(),
})
.collect(),
duration: track.duration.clone(),
album: None, // FIXME missing metadata
description: None, // FIXME missing metadata
lyrics: None, // FIXME missing metadata
})
})
}
ContentKind::Album(id) => {
self.spotify.get_album(id.as_str()).await.map(|album| {
MusicData::Album(Album {
name: album.name.clone(),
authors: album
.artists
.iter()
.map(|artist| Author {
name: artist.clone(),
})
.collect(),
description: None, // FIXME missing metadata
year: None, // FIXME missing metadata
})
})
}
ContentKind::Track(id) => self.spotify.get_track(id.as_str()).await,
_ => None,
},
None => None,
};
if track_info.is_some() {
let ti = track_info.unwrap();
let youtube_search = match self
.youtube
.search_video(
format!(
"{} {}",
ti.artists
.get(0)
.map(|artist| format!("{} -", artist))
.unwrap_or("".to_string()),
ti.name
)
.as_str(),
None,
)
.await
{
Err(_) => None,
Ok(search) => Some(search),
};
return Some(TrackItem {
spotify_track: Some(ti),
youtube_track: youtube_search.map(|search| search.items),
});
}
}
_ => todo!("Search type not present yet"), //TODO implement
}
return None;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_search_track_by_spotify_id() {
todo!("Implement me!")
}
}

View File

@ -6,6 +6,7 @@ use std::any::Any;
use std::sync::Arc;
use std::time::Duration;
#[derive(Debug, Clone)]
pub enum ContentKind {
Track(String),
Album(String),
@ -14,17 +15,20 @@ pub enum ContentKind {
Episode(String),
}
#[derive(Debug, Clone)]
pub enum PlayableKind {
Track(TrackInfo),
Podcast(EpisodeInfo),
}
#[derive(Debug, Clone)]
pub struct TrackInfo {
pub(crate) name: String,
pub(crate) artists: Vec<String>,
pub(crate) duration: Duration,
}
#[derive(Debug, Clone)]
pub struct EpisodeInfo {
pub(crate) name: String,
pub(crate) show: String,
@ -34,6 +38,7 @@ pub struct EpisodeInfo {
pub(crate) release_date: String,
}
#[derive(Debug, Clone)]
pub struct AlbumInfo {
pub(crate) name: String,
pub(crate) artists: Vec<String>,
@ -41,6 +46,7 @@ pub struct AlbumInfo {
pub(crate) tracks: Vec<TrackInfo>,
}
#[derive(Debug, Clone)]
pub struct PlaylistInfo {
pub(crate) name: String,
pub(crate) artists: Vec<String>,
@ -70,6 +76,7 @@ impl Client {
}
pub async fn get_track(&self, id: &str) -> Option<TrackInfo> {
// FIXME should we really return Option here? We're hiding a possible error or a entry not found
let track_id = match TrackId::from_id(id) {
Ok(track) => track,
Err(_e) => return None,

View File

@ -2,21 +2,22 @@ use std::error::Error;
use std::sync::Arc;
pub(crate) struct VideoSearch {
items: Vec<Video>,
pub(crate) items: Vec<Video>,
}
#[derive(Debug, Clone)]
pub(crate) struct Video {
title: String,
videoId: String,
video_id: String,
author: String,
authorId: String,
authorUrl: String,
lengthSeconds: u64,
author_id: String,
author_url: String,
length_seconds: u64,
description: String,
descriptionHtml: String,
viewCount: u64,
description_html: String,
view_count: u64,
published: u64,
publishedText: String,
published_text: String,
live_now: bool,
paid: bool,
premium: bool,
@ -88,16 +89,16 @@ impl Client {
premium,
} => Some(Video {
title: title.clone(),
videoId: videoId.clone(),
video_id: videoId.clone(),
author: author.clone(),
authorId: authorId.clone(),
authorUrl: authorUrl.clone(),
lengthSeconds: lengthSeconds.clone(),
author_id: authorId.clone(),
author_url: authorUrl.clone(),
length_seconds: lengthSeconds.clone(),
description: description.clone(),
descriptionHtml: descriptionHtml.clone(),
viewCount: viewCount.clone(),
description_html: descriptionHtml.clone(),
view_count: viewCount.clone(),
published: published.clone(),
publishedText: publishedText.clone(),
published_text: publishedText.clone(),
live_now: liveNow.clone(),
paid: paid.clone(),
premium: premium.clone(),