Change song fetching to be lazy

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/2/head
trivernis 3 years ago
parent 7dc8087dac
commit 288675c2ea
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -23,15 +23,13 @@ use shuffle::SHUFFLE_COMMAND;
use skip::SKIP_COMMAND; use skip::SKIP_COMMAND;
use crate::providers::music::queue::{MusicQueue, Song}; use crate::providers::music::queue::{MusicQueue, Song};
use crate::providers::music::responses::VideoInformation;
use crate::providers::music::{ use crate::providers::music::{
get_video_information, get_videos_for_playlist, search_video_information, get_video_information, get_videos_for_playlist, search_video_information,
}; };
use crate::utils::error::{BotError, BotResult}; use crate::utils::error::{BotError, BotResult};
use crate::utils::store::Store; use crate::utils::store::Store;
use futures::future::BoxFuture;
use futures::FutureExt;
use regex::Regex; use regex::Regex;
use std::time::Duration;
mod clear; mod clear;
mod current; mod current;
@ -121,7 +119,9 @@ struct SongEndNotifier {
#[async_trait] #[async_trait]
impl VoiceEventHandler for SongEndNotifier { impl VoiceEventHandler for SongEndNotifier {
async fn act(&self, _ctx: &EventContext<'_>) -> Option<Event> { async fn act(&self, _ctx: &EventContext<'_>) -> Option<Event> {
play_next_in_queue(&self.http, &self.channel_id, &self.queue, &self.handler).await; while !play_next_in_queue(&self.http, &self.channel_id, &self.queue, &self.handler).await {
tokio::time::sleep(Duration::from_millis(100)).await;
}
None None
} }
@ -133,17 +133,29 @@ async fn play_next_in_queue(
channel_id: &ChannelId, channel_id: &ChannelId,
queue: &Arc<Mutex<MusicQueue>>, queue: &Arc<Mutex<MusicQueue>>,
handler: &Arc<Mutex<Call>>, handler: &Arc<Mutex<Call>>,
) { ) -> bool {
let mut queue_lock = queue.lock().await; let mut queue_lock = queue.lock().await;
if let Some(next) = queue_lock.next() { if let Some(mut next) = queue_lock.next() {
let source = match songbird::ytdl(&next.url).await { let url = match next.url().await {
Some(url) => url,
None => {
let _ = channel_id
.say(&http, format!("'{}' not found", next.title()))
.await;
return false;
}
};
let source = match songbird::ytdl(&url).await {
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
let _ = channel_id let _ = channel_id
.say(&http, format!("Failed to enqueue {}: {:?}", next.title, e)) .say(
&http,
format!("Failed to enqueue {}: {:?}", next.title(), e),
)
.await; .await;
return; return false;
} }
}; };
let mut handler_lock = handler.lock().await; let mut handler_lock = handler.lock().await;
@ -152,6 +164,7 @@ async fn play_next_in_queue(
} else { } else {
queue_lock.clear_current(); queue_lock.clear_current();
} }
true
} }
/// Returns the list of songs for a given url /// Returns the list of songs for a given url
@ -177,65 +190,46 @@ async fn get_songs_for_query(ctx: &Context, msg: &Message, query: &str) -> BotRe
// if no songs were found fetch the song as a video // if no songs were found fetch the song as a video
if songs.len() == 0 { if songs.len() == 0 {
let song: Song = get_video_information(query).await?.into(); let mut song: Song = get_video_information(query).await?.into();
added_one_msg(&ctx, msg, &song).await?; added_one_msg(&ctx, msg, &mut song).await?;
songs.push(song); songs.push(song);
} else { } else {
added_multiple_msg(&ctx, msg, &mut songs).await?; added_multiple_msg(&ctx, msg, &mut songs).await?;
} }
} else if SPOTIFY_PLAYLIST_REGEX.is_match(query) { } else if SPOTIFY_PLAYLIST_REGEX.is_match(query) {
// search for all songs in the playlist and search for them on youtube // search for all songs in the playlist and search for them on youtube
let song_names = store.spotify_api.get_songs_in_playlist(query).await?; songs = store.spotify_api.get_songs_in_playlist(query).await?;
songs = parallel_search_youtube(song_names).await;
added_multiple_msg(&ctx, msg, &mut songs).await?; added_multiple_msg(&ctx, msg, &mut songs).await?;
} else if SPOTIFY_ALBUM_REGEX.is_match(query) { } else if SPOTIFY_ALBUM_REGEX.is_match(query) {
// fetch all songs in the album and search for them on youtube // fetch all songs in the album and search for them on youtube
let song_names = store.spotify_api.get_songs_in_album(query).await?; songs = store.spotify_api.get_songs_in_album(query).await?;
songs = parallel_search_youtube(song_names).await;
added_multiple_msg(&ctx, msg, &mut songs).await?; added_multiple_msg(&ctx, msg, &mut songs).await?;
} else if SPOTIFY_SONG_REGEX.is_match(query) { } else if SPOTIFY_SONG_REGEX.is_match(query) {
// fetch the song name and search it on youtube // fetch the song name and search it on youtube
let name = store.spotify_api.get_song_name(query).await?; let mut song = store.spotify_api.get_song_name(query).await?;
let song: Song = search_video_information(name.clone()) added_one_msg(ctx, msg, &mut song).await?;
.await?
.ok_or(BotError::Msg(format!("Noting found for {}", name)))?
.into();
added_one_msg(ctx, msg, &song).await?;
songs.push(song); songs.push(song);
} else { } else {
let song: Song = search_video_information(query.to_string()) let mut song: Song = search_video_information(query.to_string())
.await? .await?
.ok_or(BotError::Msg(format!("Noting found for {}", query)))? .ok_or(BotError::Msg(format!("Noting found for {}", query)))?
.into(); .into();
added_one_msg(&ctx, msg, &song).await?; added_one_msg(&ctx, msg, &mut song).await?;
songs.push(song); songs.push(song);
} }
Ok(songs) Ok(songs)
} }
/// Searches songs on youtube in parallel
async fn parallel_search_youtube(song_names: Vec<String>) -> Vec<Song> {
let search_futures: Vec<BoxFuture<BotResult<Option<VideoInformation>>>> = song_names
.into_iter()
.map(|s| search_video_information(s).boxed())
.collect();
let information: Vec<BotResult<Option<VideoInformation>>> =
futures::future::join_all(search_futures).await;
information
.into_iter()
.filter_map(|i| i.ok().and_then(|s| s).map(Song::from))
.collect()
}
/// Message when one song was added to the queue /// Message when one song was added to the queue
async fn added_one_msg(ctx: &Context, msg: &Message, song: &Song) -> BotResult<()> { async fn added_one_msg(ctx: &Context, msg: &Message, song: &mut Song) -> BotResult<()> {
let url = song.url().await.ok_or(BotError::from("Song not found"))?;
msg.channel_id msg.channel_id
.send_message(&ctx.http, |m| { .send_message(&ctx.http, |m| {
m.embed(|mut e| { m.embed(|mut e| {
e = e.description(format!("Added [{}]({}) to the queue", song.title, song.url)); e = e.description(format!("Added [{}]({}) to the queue", song.title(), url));
if let Some(thumb) = &song.thumbnail { if let Some(thumb) = &song.thumbnail() {
e = e.thumbnail(thumb); e = e.thumbnail(thumb);
} }

@ -1,8 +1,8 @@
use std::cmp::min; use std::cmp::min;
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::CommandResult;
use serenity::framework::standard::macros::command; use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult;
use serenity::model::channel::Message; use serenity::model::channel::Message;
use crate::commands::music::get_queue_for_guild; use crate::commands::music::get_queue_for_guild;
@ -20,7 +20,7 @@ async fn queue(ctx: &Context, msg: &Message) -> CommandResult {
let songs: Vec<(usize, String)> = queue_lock let songs: Vec<(usize, String)> = queue_lock
.entries() .entries()
.into_iter() .into_iter()
.map(|s| s.title.clone()) .map(|s| s.title().clone())
.enumerate() .enumerate()
.collect(); .collect();

@ -1,5 +1,8 @@
use crate::providers::music::queue::Song;
use crate::providers::music::responses::{PlaylistEntry, VideoInformation}; use crate::providers::music::responses::{PlaylistEntry, VideoInformation};
use crate::utils::error::BotResult; use crate::utils::error::BotResult;
use futures::future::BoxFuture;
use futures::FutureExt;
use std::process::Stdio; use std::process::Stdio;
use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Arc; use std::sync::Arc;
@ -49,6 +52,21 @@ pub(crate) async fn search_video_information(query: String) -> BotResult<Option<
Ok(information) Ok(information)
} }
/// Searches songs on youtube in parallel
#[allow(dead_code)]
async fn parallel_search_youtube(song_names: Vec<String>) -> Vec<Song> {
let search_futures: Vec<BoxFuture<BotResult<Option<VideoInformation>>>> = song_names
.into_iter()
.map(|s| search_video_information(s).boxed())
.collect();
let information: Vec<BotResult<Option<VideoInformation>>> =
futures::future::join_all(search_futures).await;
information
.into_iter()
.filter_map(|i| i.ok().and_then(|s| s).map(Song::from))
.collect()
}
/// Executes youtube-dl asynchronously /// Executes youtube-dl asynchronously
/// An atomic U8 is used to control the number of parallel processes /// An atomic U8 is used to control the number of parallel processes
/// to avoid using too much memory /// to avoid using too much memory

@ -3,7 +3,9 @@ use std::collections::VecDeque;
use songbird::tracks::TrackHandle; use songbird::tracks::TrackHandle;
use crate::providers::music::responses::{PlaylistEntry, VideoInformation}; use crate::providers::music::responses::{PlaylistEntry, VideoInformation};
use crate::providers::music::search_video_information;
use crate::utils::shuffle_vec_deque; use crate::utils::shuffle_vec_deque;
use aspotify::{Track, TrackSimplified};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct MusicQueue { pub struct MusicQueue {
@ -67,16 +69,52 @@ impl MusicQueue {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Song { pub struct Song {
pub url: String, url: Option<String>,
pub title: String, title: String,
pub author: String, author: String,
pub thumbnail: Option<String>, thumbnail: Option<String>,
}
impl Song {
/// The url of the song
/// fetched when not available
pub async fn url(&mut self) -> Option<String> {
if let Some(url) = self.url.clone() {
Some(url)
} else {
let information = search_video_information(format!("{} - {}", self.author, self.title))
.await
.ok()
.and_then(|i| i)?;
self.url = Some(information.webpage_url.clone());
self.thumbnail = information.thumbnail;
self.author = information.uploader;
Some(information.webpage_url)
}
}
/// The title of the song
pub fn title(&self) -> &String {
&self.title
}
#[allow(dead_code)]
/// the author of the song
pub fn author(&self) -> &String {
&self.author
}
/// The thumbnail of the song
pub fn thumbnail(&self) -> &Option<String> {
&self.thumbnail
}
} }
impl From<VideoInformation> for Song { impl From<VideoInformation> for Song {
fn from(info: VideoInformation) -> Self { fn from(info: VideoInformation) -> Self {
Self { Self {
url: info.webpage_url, url: Some(info.webpage_url),
title: info.title, title: info.title,
author: info.uploader, author: info.uploader,
thumbnail: info.thumbnail, thumbnail: info.thumbnail,
@ -87,10 +125,42 @@ impl From<VideoInformation> for Song {
impl From<PlaylistEntry> for Song { impl From<PlaylistEntry> for Song {
fn from(entry: PlaylistEntry) -> Self { fn from(entry: PlaylistEntry) -> Self {
Self { Self {
url: format!("https://www.youtube.com/watch?v={}", entry.url), url: Some(format!("https://www.youtube.com/watch?v={}", entry.url)),
title: entry.title, title: entry.title,
author: entry.uploader, author: entry.uploader,
thumbnail: None, thumbnail: None,
} }
} }
} }
impl From<Track> for Song {
fn from(track: Track) -> Self {
Self {
title: track.name,
author: track
.artists
.into_iter()
.map(|a| a.name)
.collect::<Vec<String>>()
.join(" & "),
url: None,
thumbnail: None,
}
}
}
impl From<TrackSimplified> for Song {
fn from(track: TrackSimplified) -> Self {
Self {
title: track.name,
author: track
.artists
.into_iter()
.map(|a| a.name)
.collect::<Vec<String>>()
.join(" & "),
url: None,
thumbnail: None,
}
}
}

@ -1,3 +1,4 @@
use crate::providers::music::queue::Song;
use crate::utils::error::{BotError, BotResult}; use crate::utils::error::{BotError, BotResult};
use aspotify::{Client, ClientCredentials, PlaylistItem, PlaylistItemType}; use aspotify::{Client, ClientCredentials, PlaylistItem, PlaylistItemType};
@ -19,7 +20,7 @@ impl SpotifyApi {
} }
/// Returns the song names for a playlist /// Returns the song names for a playlist
pub async fn get_songs_in_playlist(&self, url: &str) -> BotResult<Vec<String>> { pub async fn get_songs_in_playlist(&self, url: &str) -> BotResult<Vec<Song>> {
let id = self.get_id_for_url(url)?; let id = self.get_id_for_url(url)?;
let mut playlist_tracks = Vec::new(); let mut playlist_tracks = Vec::new();
let mut offset = 0; let mut offset = 0;
@ -36,17 +37,9 @@ impl SpotifyApi {
let song_names = playlist_tracks let song_names = playlist_tracks
.into_iter() .into_iter()
.filter_map(|item| item.item) .filter_map(|item| item.item)
.map(|t| match t { .filter_map(|t| match t {
PlaylistItemType::Track(t) => format!( PlaylistItemType::Track(t) => Some(Song::from(t)),
"{} - {}", PlaylistItemType::Episode(_) => None,
t.artists
.into_iter()
.map(|a| a.name)
.collect::<Vec<String>>()
.join(" & "),
t.name
),
PlaylistItemType::Episode(e) => e.name,
}) })
.collect(); .collect();
@ -71,44 +64,20 @@ impl SpotifyApi {
} }
/// Returns all song names for a given album /// Returns all song names for a given album
pub async fn get_songs_in_album(&self, url: &str) -> BotResult<Vec<String>> { pub async fn get_songs_in_album(&self, url: &str) -> BotResult<Vec<Song>> {
let id = self.get_id_for_url(url)?; let id = self.get_id_for_url(url)?;
let album = self.client.albums().get_album(&*id, None).await?.data; let album = self.client.albums().get_album(&*id, None).await?.data;
let song_names = album let song_names = album.tracks.items.into_iter().map(Song::from).collect();
.tracks
.items
.into_iter()
.map(|item| {
format!(
"{} - {}",
item.artists
.into_iter()
.map(|a| a.name)
.collect::<Vec<String>>()
.join(" & "),
item.name
)
})
.collect();
Ok(song_names) Ok(song_names)
} }
/// Returns the name for a spotify song url /// Returns the name for a spotify song url
pub async fn get_song_name(&self, url: &str) -> BotResult<String> { pub async fn get_song_name(&self, url: &str) -> BotResult<Song> {
let id = self.get_id_for_url(url)?; let id = self.get_id_for_url(url)?;
let track = self.client.tracks().get_track(&*id, None).await?.data; let track = self.client.tracks().get_track(&*id, None).await?.data;
Ok(format!( Ok(track.into())
"{} - {}",
track
.artists
.into_iter()
.map(|a| a.name)
.collect::<Vec<String>>()
.join(" & "),
track.name
))
} }
/// Returns the id for a given spotify URL /// Returns the id for a given spotify URL

Loading…
Cancel
Save