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 crate::providers::music::queue::{MusicQueue, Song};
use crate::providers::music::responses::VideoInformation;
use crate::providers::music::{
get_video_information, get_videos_for_playlist, search_video_information,
};
use crate::utils::error::{BotError, BotResult};
use crate::utils::store::Store;
use futures::future::BoxFuture;
use futures::FutureExt;
use regex::Regex;
use std::time::Duration;
mod clear;
mod current;
@ -121,7 +119,9 @@ struct SongEndNotifier {
#[async_trait]
impl VoiceEventHandler for SongEndNotifier {
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
}
@ -133,17 +133,29 @@ async fn play_next_in_queue(
channel_id: &ChannelId,
queue: &Arc<Mutex<MusicQueue>>,
handler: &Arc<Mutex<Call>>,
) {
) -> bool {
let mut queue_lock = queue.lock().await;
if let Some(next) = queue_lock.next() {
let source = match songbird::ytdl(&next.url).await {
if let Some(mut next) = queue_lock.next() {
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,
Err(e) => {
let _ = channel_id
.say(&http, format!("Failed to enqueue {}: {:?}", next.title, e))
.say(
&http,
format!("Failed to enqueue {}: {:?}", next.title(), e),
)
.await;
return;
return false;
}
};
let mut handler_lock = handler.lock().await;
@ -152,6 +164,7 @@ async fn play_next_in_queue(
} else {
queue_lock.clear_current();
}
true
}
/// 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 songs.len() == 0 {
let song: Song = get_video_information(query).await?.into();
added_one_msg(&ctx, msg, &song).await?;
let mut song: Song = get_video_information(query).await?.into();
added_one_msg(&ctx, msg, &mut song).await?;
songs.push(song);
} else {
added_multiple_msg(&ctx, msg, &mut songs).await?;
}
} else if SPOTIFY_PLAYLIST_REGEX.is_match(query) {
// 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 = parallel_search_youtube(song_names).await;
songs = store.spotify_api.get_songs_in_playlist(query).await?;
added_multiple_msg(&ctx, msg, &mut songs).await?;
} else if SPOTIFY_ALBUM_REGEX.is_match(query) {
// 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 = parallel_search_youtube(song_names).await;
songs = store.spotify_api.get_songs_in_album(query).await?;
added_multiple_msg(&ctx, msg, &mut songs).await?;
} else if SPOTIFY_SONG_REGEX.is_match(query) {
// fetch the song name and search it on youtube
let name = store.spotify_api.get_song_name(query).await?;
let song: Song = search_video_information(name.clone())
.await?
.ok_or(BotError::Msg(format!("Noting found for {}", name)))?
.into();
added_one_msg(ctx, msg, &song).await?;
let mut song = store.spotify_api.get_song_name(query).await?;
added_one_msg(ctx, msg, &mut song).await?;
songs.push(song);
} else {
let song: Song = search_video_information(query.to_string())
let mut song: Song = search_video_information(query.to_string())
.await?
.ok_or(BotError::Msg(format!("Noting found for {}", query)))?
.into();
added_one_msg(&ctx, msg, &song).await?;
added_one_msg(&ctx, msg, &mut song).await?;
songs.push(song);
}
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
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
.send_message(&ctx.http, |m| {
m.embed(|mut e| {
e = e.description(format!("Added [{}]({}) to the queue", song.title, song.url));
if let Some(thumb) = &song.thumbnail {
e = e.description(format!("Added [{}]({}) to the queue", song.title(), url));
if let Some(thumb) = &song.thumbnail() {
e = e.thumbnail(thumb);
}

@ -1,8 +1,8 @@
use std::cmp::min;
use serenity::client::Context;
use serenity::framework::standard::CommandResult;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult;
use serenity::model::channel::Message;
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
.entries()
.into_iter()
.map(|s| s.title.clone())
.map(|s| s.title().clone())
.enumerate()
.collect();

@ -1,5 +1,8 @@
use crate::providers::music::queue::Song;
use crate::providers::music::responses::{PlaylistEntry, VideoInformation};
use crate::utils::error::BotResult;
use futures::future::BoxFuture;
use futures::FutureExt;
use std::process::Stdio;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Arc;
@ -49,6 +52,21 @@ pub(crate) async fn search_video_information(query: String) -> BotResult<Option<
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
/// An atomic U8 is used to control the number of parallel processes
/// to avoid using too much memory

@ -3,7 +3,9 @@ use std::collections::VecDeque;
use songbird::tracks::TrackHandle;
use crate::providers::music::responses::{PlaylistEntry, VideoInformation};
use crate::providers::music::search_video_information;
use crate::utils::shuffle_vec_deque;
use aspotify::{Track, TrackSimplified};
#[derive(Clone, Debug)]
pub struct MusicQueue {
@ -67,16 +69,52 @@ impl MusicQueue {
#[derive(Clone, Debug)]
pub struct Song {
pub url: String,
pub title: String,
pub author: String,
pub thumbnail: Option<String>,
url: Option<String>,
title: String,
author: 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 {
fn from(info: VideoInformation) -> Self {
Self {
url: info.webpage_url,
url: Some(info.webpage_url),
title: info.title,
author: info.uploader,
thumbnail: info.thumbnail,
@ -87,10 +125,42 @@ impl From<VideoInformation> for Song {
impl From<PlaylistEntry> for Song {
fn from(entry: PlaylistEntry) -> 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,
author: entry.uploader,
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 aspotify::{Client, ClientCredentials, PlaylistItem, PlaylistItemType};
@ -19,7 +20,7 @@ impl SpotifyApi {
}
/// 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 mut playlist_tracks = Vec::new();
let mut offset = 0;
@ -36,17 +37,9 @@ impl SpotifyApi {
let song_names = playlist_tracks
.into_iter()
.filter_map(|item| item.item)
.map(|t| match t {
PlaylistItemType::Track(t) => format!(
"{} - {}",
t.artists
.into_iter()
.map(|a| a.name)
.collect::<Vec<String>>()
.join(" & "),
t.name
),
PlaylistItemType::Episode(e) => e.name,
.filter_map(|t| match t {
PlaylistItemType::Track(t) => Some(Song::from(t)),
PlaylistItemType::Episode(_) => None,
})
.collect();
@ -71,44 +64,20 @@ impl SpotifyApi {
}
/// 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 album = self.client.albums().get_album(&*id, None).await?.data;
let song_names = album
.tracks
.items
.into_iter()
.map(|item| {
format!(
"{} - {}",
item.artists
.into_iter()
.map(|a| a.name)
.collect::<Vec<String>>()
.join(" & "),
item.name
)
})
.collect();
let song_names = album.tracks.items.into_iter().map(Song::from).collect();
Ok(song_names)
}
/// 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 track = self.client.tracks().get_track(&*id, None).await?.data;
Ok(format!(
"{} - {}",
track
.artists
.into_iter()
.map(|a| a.name)
.collect::<Vec<String>>()
.join(" & "),
track.name
))
Ok(track.into())
}
/// Returns the id for a given spotify URL

Loading…
Cancel
Save