From ba0f1fb959a673c292b040eebbac4b1f087d732a Mon Sep 17 00:00:00 2001 From: moonleay Date: Sun, 10 Mar 2024 19:36:02 +0100 Subject: [PATCH] feat: added skip command, reworked embed messages fix: fixed issue with queue not working properly --- src/commands/info.rs | 6 +- src/commands/mod.rs | 3 +- src/commands/play.rs | 4 +- src/commands/skip.rs | 24 ++++++ src/commands/stop.rs | 2 +- src/main.rs | 8 +- src/music/music_events.rs | 7 +- src/music/music_manager.rs | 150 +++++++++++++++++++------------------ src/util/config.rs | 6 -- src/util/embed.rs | 63 +++++++++++----- 10 files changed, 159 insertions(+), 114 deletions(-) create mode 100644 src/commands/skip.rs diff --git a/src/commands/info.rs b/src/commands/info.rs index 2c93d36..0602288 100644 --- a/src/commands/info.rs +++ b/src/commands/info.rs @@ -6,11 +6,7 @@ use crate::util::embed::Embed; pub async fn run(_ctx: &Context, command: &CommandInteraction) -> CreateEmbed { let username = command.user.name.as_str(); - Embed::create( - "", - "Botendo v7", - "developed by [moonleay](https://moonleay.net)\n\nCheck out the repository: https://git.moonleay.net/DiscordBots/Rustendo", - ) + Embed::create_success_response(username, "Botendo v7", "developed by [moonleay](https://moonleay.net)\n\nCheck out the repository: https://git.moonleay.net/DiscordBots/Rustendo") } pub fn register() -> CreateCommand { diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 6eaf7b9..e2ebff8 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,3 +1,4 @@ pub mod info; pub mod play; -pub mod stop; \ No newline at end of file +pub mod stop; +pub mod skip; \ No newline at end of file diff --git a/src/commands/play.rs b/src/commands/play.rs index 8613fd2..35fdc1c 100644 --- a/src/commands/play.rs +++ b/src/commands/play.rs @@ -19,13 +19,13 @@ pub async unsafe fn run(ctx: &Context, command: &CommandInteraction) -> CreateEm }); if query.is_none() { - return Embed::create(username, "Error 400", "There is no query provided"); + return Embed::create_error_respose(username, "400: Bad request", "There is no query provided"); } let guild_id = match &command.guild_id { Some(guild_id) => guild_id, None => { - return Embed::create(username, "GuildId not found", "Could not find guild id."); + return Embed::create_error_respose(username, "guild_id not found", "Could not find guild id."); } }; diff --git a/src/commands/skip.rs b/src/commands/skip.rs new file mode 100644 index 0000000..3598dc5 --- /dev/null +++ b/src/commands/skip.rs @@ -0,0 +1,24 @@ +use crate::music::music_manager; +use crate::util::embed::Embed; +use serenity::all::{CommandInteraction, Context}; +use serenity::builder::{CreateCommand, CreateEmbed}; + +pub async unsafe fn run(ctx: &Context, command: &CommandInteraction) -> CreateEmbed { + let username = command.user.name.as_str(); + + let guild_id = match &command.guild_id { + Some(guild_id) => guild_id, + None => { + return Embed::create_error_respose(username, "guild_id not found", "Could not find guild id."); + } + }; + + music_manager::attempt_to_skip_current_song(ctx, guild_id, &command.user.id, &command.user.name).await +} + +pub fn register() -> CreateCommand { + CreateCommand::new("skip").description("Skip to the next song in queue") +} + +// >18/02/2024 @ 19:01:59 - bartlo +// >2024-02-19 17:58:39 | moonleay diff --git a/src/commands/stop.rs b/src/commands/stop.rs index 8a65e35..f4bc46a 100644 --- a/src/commands/stop.rs +++ b/src/commands/stop.rs @@ -9,7 +9,7 @@ pub async unsafe fn run(ctx: &Context, command: &CommandInteraction) -> CreateEm let guild_id = match &command.guild_id { Some(guild_id) => guild_id, None => { - return Embed::create(username, "GuildId not found", "Could not find guild id."); + return Embed::create_error_respose(username, "guild_id not found", "Could not find guild id."); } }; diff --git a/src/main.rs b/src/main.rs index 55eeaf4..6283d41 100644 --- a/src/main.rs +++ b/src/main.rs @@ -71,6 +71,7 @@ impl EventHandler for Handler { "info" => commands::info::run(&ctx, &command).await, "play" => commands::play::run(&ctx, &command).await, "stop" => commands::stop::run(&ctx, &command).await, + "skip" => commands::skip::run(&ctx, &command).await, _ => respond_with_error(&ctx, &command).await, }); @@ -90,6 +91,7 @@ impl EventHandler for Handler { let _command = Command::create_global_command(&ctx.http, commands::info::register()).await; let _command = Command::create_global_command(&ctx.http, commands::stop::register()).await; let _command = Command::create_global_command(&ctx.http, commands::play::register()).await; + let _command = Command::create_global_command(&ctx.http, commands::skip::register()).await; println!("Commands are registered and Rustendo is ready for Freddy."); } @@ -120,11 +122,7 @@ impl EventHandler for Handler { } pub async fn respond_with_error(_ctx: &Context, command: &CommandInteraction) -> CreateEmbed { - Embed::create( - command.user.name.to_owned(), - "Command not found", - "Cannot find the executed command", - ) + Embed::create_error_respose(command.user.name.as_str(), "Command not found", "Cannot find the executed command") } #[tokio::main] diff --git a/src/music/music_events.rs b/src/music/music_events.rs index 3b52714..f9e43cb 100644 --- a/src/music/music_events.rs +++ b/src/music/music_events.rs @@ -1,6 +1,7 @@ use crate::music::{music_manager, music_queue}; use serenity::all::{ChannelId, GuildId, Http}; use serenity::async_trait; +use songbird::input::Compose; use songbird::{Event, EventContext, EventHandler}; use std::sync::Arc; @@ -21,7 +22,7 @@ impl EventHandler for TrackEndNotifier { let q = &music_queue.queue; if q.is_empty() { // No more songs in queue, exit the vc - let stopped = match music_manager::stop(&self.cmdctx, &self.guild_id).await { + let stopped = match music_manager::leave(&self.cmdctx, &self.guild_id).await { Ok(stopped) => stopped, Err(e) => { println!("Cannot stop: {:?}", e); @@ -29,6 +30,7 @@ impl EventHandler for TrackEndNotifier { } }; if stopped { + music_queue::delete_queue(&self.guild_id).await; println!("Stopped playing successfully."); } else { println!("Failed to stop playing."); @@ -40,7 +42,8 @@ impl EventHandler for TrackEndNotifier { println!("Cannot get head of queue"); return None; } - let head = head.unwrap(); + let mut head = head.unwrap(); + println!("Now playing: {}", head.aux_metadata().await.unwrap().title.unwrap()); music_manager::play_song(&self.cmdctx, &self.guild_id, &head).await; } diff --git a/src/music/music_manager.rs b/src/music/music_manager.rs index 2f42673..74a1b3c 100644 --- a/src/music/music_manager.rs +++ b/src/music/music_manager.rs @@ -5,11 +5,12 @@ use crate::util::user_util::get_vc_id; use crate::HttpKey; use serenity::all::{Context, CreateEmbed, GuildId, UserId}; use songbird::error::JoinError; -use songbird::input::{Compose, YoutubeDl}; +use songbird::input::YoutubeDl; use songbird::{Event, TrackEvent}; use std::sync::Arc; -use std::time::Duration; + +/// Either queues the song, or start playing it instantly, depending on if there is already a song playing pub async fn attempt_to_queue_song( ctx: &Context, guild_id: &GuildId, @@ -18,21 +19,17 @@ pub async fn attempt_to_queue_song( query: &str, ) -> CreateEmbed { if !user_util::is_user_connected_to_vc(ctx, guild_id, user_id).await { - return Embed::create( - username, - "You are not connected to a VC", - "Connect to my VC to control the music.", - ); + return Embed::create_error_respose(username, "You are not connected to a VC", "Connect to my vc to start controlling the music.") } let connect_to = match get_vc_id(ctx, guild_id, user_id).await { Some(channel_id) => channel_id, None => { - return Embed::create(username, "Error", "Cannot get channel id"); + return Embed::create_error_respose(username, "Error", "Cannot find channel_id."); } }; - let manager = &songbird::get(ctx) // TODO match + let manager: &Arc = &songbird::get(ctx) // TODO match .await .expect("Cannot get Songbird.") .clone(); @@ -42,23 +39,18 @@ pub async fn attempt_to_queue_song( // self is connected to vc, check if user is in same vc if self_channel.is_none() { - // TODO This could maybe be removed? // Connect to VC - manager + manager // TODO match .join(*guild_id, connect_to) .await .expect("Cannot connect>..."); } } else { - let self_channel = self_channel.expect("Cannot get self channel"); + let self_channel = self_channel.expect("Cannot get self channel");// TODO: match // Check if user is in the same VC as the bot if self_channel != connect_to { - return Embed::create( - username, - "You are not in my VC.", - "Connect to my VC to control the music.", - ); + return Embed::create_error_respose(username, "You are not in my VC", "You have to be in my VC in order to controll the music.") } } @@ -72,7 +64,7 @@ pub async fn attempt_to_queue_song( }; // Create source - let mut src = if do_search { + let src = if do_search { YoutubeDl::new_search(http_client, query.to_string()) } else { YoutubeDl::new(http_client, query.to_string()) @@ -82,11 +74,7 @@ pub async fn attempt_to_queue_song( music_queue::add_to_queue(guild_id, src.clone()).await; if currently_playing.is_some() { // Add to queue - return Embed::create( - username, - "Added to queue", - "The song was added to the queue.", - ); + return Embed::create_yt_playing(src, username, "Added to queue").await; } let _query = music_queue::get_head(guild_id) @@ -97,7 +85,7 @@ pub async fn attempt_to_queue_song( let handler_lock = match manager.get(*guild_id) { Some(handler) => handler, None => { - return Embed::create("", "Error", "Cannot get handler"); + return Embed::create_error_respose(username, "Error", "Cannot get handler of this guild."); } }; @@ -114,33 +102,10 @@ pub async fn attempt_to_queue_song( }, ); - // Get metadata - let metadata = src.aux_metadata().await.expect("Cannot get metadata"); - let title = metadata.title.unwrap_or("Unknown title".to_string()); - let author = metadata.artist.unwrap_or("Unknown artist".to_string()); - let duration = metadata.duration.unwrap_or(Duration::from_millis(0)); - let thumbnail = metadata - .thumbnail - .unwrap_or("https://http.cat/images/404.jpg".to_string()); - let link = metadata - .source_url - .unwrap_or("https://piped.moonleay.net/404".to_string()); - - Embed::create( - username, - "Added to queue", - format!( - "{} by {} ({}min {}sec) was added to the queue.\n [[Link]({})]", - title, - author, - duration.as_secs() / 60, - duration.as_secs() % 60, - link - ), - ) - .thumbnail(thumbnail) + Embed::create_yt_playing(src, username, "Now playing").await } +/// Play the provided song pub async fn play_song(ctx: &Context, guild_id: &GuildId, target: &YoutubeDl) { let manager = &songbird::get(ctx) // TODO match .await @@ -159,9 +124,60 @@ pub async fn play_song(ctx: &Context, guild_id: &GuildId, target: &YoutubeDl) { }; let mut handler = handler_lock.lock().await; + let _ = handler.stop(); // Stop playing the current song let _ = handler.play_input(target.clone().into()); // TODO: Add event handlers } +/// Attempt to skip the song, which is currently playing. Do nothing if there is no next song +pub async fn attempt_to_skip_current_song(ctx: &Context, guild_id: &GuildId, user_id: &UserId, username: &str) -> CreateEmbed { + if !user_util::is_user_connected_to_vc(ctx, guild_id, user_id).await { + return Embed::create_error_respose(username, "You are not connected to a VC", "Connect to my vc to start controlling the music.") + } + + let connect_to = match get_vc_id(ctx, guild_id, user_id).await { + Some(channel_id) => channel_id, + None => { + return Embed::create_error_respose(username, "Error", "Cannot find channel_id."); + } + }; + + let manager: &Arc = &songbird::get(ctx) // TODO match + .await + .expect("Cannot get Songbird.") + .clone(); + + let self_channel = user_util::get_self_vc_id(ctx, guild_id).await; + if !user_util::is_self_connected_to_vc(ctx, guild_id).await { + // self is connected to vc, check if user is in same vc + + if self_channel.is_none() { + // Connect to VC + manager // TODO match + .join(*guild_id, connect_to) + .await + .expect("Cannot connect>..."); + } + } else { + let self_channel = self_channel.expect("Cannot get self channel");// TODO: match + + // Check if user is in the same VC as the bot + if self_channel != connect_to { + return Embed::create_error_respose(username, "You are not in my VC", "You have to be in my VC in order to controll the music.") + } + } + + let head = music_queue::get_head(guild_id).await; // TODO match + if head.is_none() { + return Embed::create_error_respose(username, "Cannot find a song to play", "The queue is empty."); + } + let head = head.unwrap(); + play_song(ctx, guild_id, &head).await; + + Embed::create_yt_playing(head, username, "Song skipped; Now playing").await +} + + +/// Try to clear the queue and stop playing. Also leave the vc pub async fn attempt_to_stop( ctx: &Context, guild_id: &GuildId, @@ -170,11 +186,7 @@ pub async fn attempt_to_stop( ) -> CreateEmbed { if !user_util::is_self_connected_to_vc(ctx, guild_id).await { // Bot is not connectd to vc; no need to dc - return Embed::create( - username, - "Bot is not connected", - "And therefore there is no need to do anything.", - ); + return Embed::create_error_respose(username, "Bot is not connected", "And therefore there is no need to do anything."); } let self_channel = user_util::get_self_vc_id(ctx, guild_id) .await @@ -185,48 +197,38 @@ pub async fn attempt_to_stop( // Check if user is in the same VC as the bot if self_channel != connect_to { - return Embed::create( - username, - "You are not in my VC.", - "Connect to my VC to control the music.", - ); + return Embed::create_error_respose(username, "You are not in my VC.", "Connect to my VC to controll the music."); } - let stopped = match stop(ctx, guild_id).await { + let stopped = match leave(ctx, guild_id).await { Ok(stopped) => stopped, Err(e) => { println!("Error while stopping: {:?}", e); - return Embed::create( - username, - "There was an error", - "Tell moonleay to check the logs.".to_string(), - ); + return Embed::create_error_respose(username, "There was an error", "Tell moonleay to check the logs."); } }; if !stopped { - Embed::create( - username, - "Can't stop, what ain't running.", - "I am not connected. I cant stop doing something, when I'm not doing it".to_string(), - ) + Embed::create_error_respose(username, "Can't stop, what ain't running", "I am not connected.\nI cant stop doing something, when I'm not doing it.") } else { music_queue::delete_queue(guild_id).await; // Clear queue - Embed::create(username, "I stopped and left", "Just like your girlfriend.") + Embed::create_success_response(username, "I stopped and left", "Just like you girlfriend.") } } -// Make the bot leave the voice channel. Returns Ok(true) if bot was connected, returns Ok(false) if bot was not connected. Returns Err if something went wrong. -pub async fn stop(ctx: &Context, guild_id: &GuildId) -> Result { +/// Make the bot leave the voice channel. Returns Ok(true) if bot was connected, returns Ok(false) if bot was not connected. Returns Err if something went wrong. +pub async fn leave(ctx: &Context, guild_id: &GuildId) -> Result { let manager = songbird::get(ctx) .await .expect("Cannot get Songbird") .clone(); - let has_handler = manager.get(*guild_id).is_some(); + let handler = manager.get(*guild_id); + let has_handler = handler.is_some(); if has_handler { + handler.unwrap().lock().await.stop(); manager.remove(*guild_id).await?; return Ok(true); // Handler removed } diff --git a/src/util/config.rs b/src/util/config.rs index f3e4cb9..f5dc260 100644 --- a/src/util/config.rs +++ b/src/util/config.rs @@ -6,9 +6,6 @@ use std::error::Error; #[derive(Deserialize, Serialize)] pub struct Config { pub discord_token: String, - pub lavalink_address: String, - pub lavalink_password: String, - pub user_id: u64, } const CONFIG_FILE: &str = "./data/config.json"; @@ -26,9 +23,6 @@ pub fn load() -> Result> { fn create_empty() -> fs::File { let example_config = Config { discord_token: "paste_your_token".to_string(), - lavalink_address: "paste_your_lavalink_address".to_string(), - lavalink_password: "paste_your_lavalink_password".to_string(), - user_id: 1, }; let mut config_file = fs::File::create(CONFIG_FILE).unwrap(); diff --git a/src/util/embed.rs b/src/util/embed.rs index bfdb2f1..ffb68a5 100644 --- a/src/util/embed.rs +++ b/src/util/embed.rs @@ -1,29 +1,56 @@ -use std::fmt::Display; +use std::time::Duration; use chrono::Local; -use serenity::all::{CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter}; +use serenity::all::{Color, CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter}; +use songbird::input::{Compose, YoutubeDl}; pub struct Embed; impl Embed { - pub fn create< - S: Into + Display, - T: Into + Display, - U: Into + Display, - >( - username: S, - title: T, - message: U, - ) -> CreateEmbed { + pub fn create_success_response(username: &str, title: &str, desc: &str) -> CreateEmbed { let current_time = Local::now().format("%Y-%m-%d @ %H:%M:%S"); CreateEmbed::new() - .author(CreateEmbedAuthor::new(username.to_string())) - .title(title) - .description(message) - .footer(CreateEmbedFooter::new(format!( - "> {} - {}", - current_time, username - ))) + .title(title) + .description(desc) + .color(Color::from_rgb(224, 49, 26)) + .footer(CreateEmbedFooter::new(format!("> {} - {}", current_time, username))) + } + + pub fn create_error_respose(username: &str, error_title: &str, error_desc: &str) -> CreateEmbed { + let current_time = Local::now().format("%Y-%m-%d @ %H:%M:%S"); + + CreateEmbed::new() + .author(CreateEmbedAuthor::new("Oops, something went wrong.")) + .title(error_title) + .description(error_desc) + .color(Color::from_rgb(224, 49, 26)) + .footer(CreateEmbedFooter::new(format!("> {} - {}", current_time, username))) + } + + pub async fn create_yt_playing(mut src: YoutubeDl, username: &str, show_as_author: &str) -> CreateEmbed { + let current_time = Local::now().format("%Y-%m-%d @ %H:%M:%S"); + + // Get metadata + let metadata = src.aux_metadata().await.expect("Cannot get metadata"); + let title = metadata.title.unwrap_or("Unknown title".to_string()); + let artist = metadata.artist.unwrap_or("Unknown artist".to_string()); + let duration = metadata.duration.unwrap_or(Duration::from_millis(0)); + let thumbnail = metadata + .thumbnail + .unwrap_or("https://http.cat/images/403.jpg".to_string()); + let link = metadata + .source_url + .unwrap_or("https://piped.moonleay.net/403".to_string()); + + CreateEmbed::new() + .author(CreateEmbedAuthor::new(show_as_author)) + .title(title) + .url(link) + .thumbnail(thumbnail) + .field("Artist", artist, true) + .field("Duration", format!("{}min {}sec", duration.as_secs() / 58, duration.as_secs() % 59), true) + .color(Color::from_rgb(81, 224, 26)) + .footer(CreateEmbedFooter::new(format!("> {} - {}",current_time, username))) } }