feat: added skip command, reworked embed messages

fix: fixed issue with queue not working properly
This commit is contained in:
moonleay 2024-03-10 19:36:02 +01:00
parent 1dcd0ab66b
commit ba0f1fb959
Signed by: moonleay
GPG key ID: 82667543CCD715FB
10 changed files with 159 additions and 114 deletions

View file

@ -6,11 +6,7 @@ use crate::util::embed::Embed;
pub async fn run(_ctx: &Context, command: &CommandInteraction) -> CreateEmbed { pub async fn run(_ctx: &Context, command: &CommandInteraction) -> CreateEmbed {
let username = command.user.name.as_str(); let username = command.user.name.as_str();
Embed::create( Embed::create_success_response(username, "Botendo v7", "developed by [moonleay](https://moonleay.net)\n\nCheck out the repository: https://git.moonleay.net/DiscordBots/Rustendo")
"",
"Botendo v7",
"developed by [moonleay](https://moonleay.net)\n\nCheck out the repository: https://git.moonleay.net/DiscordBots/Rustendo",
)
} }
pub fn register() -> CreateCommand { pub fn register() -> CreateCommand {

View file

@ -1,3 +1,4 @@
pub mod info; pub mod info;
pub mod play; pub mod play;
pub mod stop; pub mod stop;
pub mod skip;

View file

@ -19,13 +19,13 @@ pub async unsafe fn run(ctx: &Context, command: &CommandInteraction) -> CreateEm
}); });
if query.is_none() { 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 { let guild_id = match &command.guild_id {
Some(guild_id) => guild_id, Some(guild_id) => guild_id,
None => { 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.");
} }
}; };

24
src/commands/skip.rs Normal file
View file

@ -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

View file

@ -9,7 +9,7 @@ pub async unsafe fn run(ctx: &Context, command: &CommandInteraction) -> CreateEm
let guild_id = match &command.guild_id { let guild_id = match &command.guild_id {
Some(guild_id) => guild_id, Some(guild_id) => guild_id,
None => { 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.");
} }
}; };

View file

@ -71,6 +71,7 @@ impl EventHandler for Handler {
"info" => commands::info::run(&ctx, &command).await, "info" => commands::info::run(&ctx, &command).await,
"play" => commands::play::run(&ctx, &command).await, "play" => commands::play::run(&ctx, &command).await,
"stop" => commands::stop::run(&ctx, &command).await, "stop" => commands::stop::run(&ctx, &command).await,
"skip" => commands::skip::run(&ctx, &command).await,
_ => respond_with_error(&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::info::register()).await;
let _command = Command::create_global_command(&ctx.http, commands::stop::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::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."); 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 { pub async fn respond_with_error(_ctx: &Context, command: &CommandInteraction) -> CreateEmbed {
Embed::create( Embed::create_error_respose(command.user.name.as_str(), "Command not found", "Cannot find the executed command")
command.user.name.to_owned(),
"Command not found",
"Cannot find the executed command",
)
} }
#[tokio::main] #[tokio::main]

View file

@ -1,6 +1,7 @@
use crate::music::{music_manager, music_queue}; use crate::music::{music_manager, music_queue};
use serenity::all::{ChannelId, GuildId, Http}; use serenity::all::{ChannelId, GuildId, Http};
use serenity::async_trait; use serenity::async_trait;
use songbird::input::Compose;
use songbird::{Event, EventContext, EventHandler}; use songbird::{Event, EventContext, EventHandler};
use std::sync::Arc; use std::sync::Arc;
@ -21,7 +22,7 @@ impl EventHandler for TrackEndNotifier {
let q = &music_queue.queue; let q = &music_queue.queue;
if q.is_empty() { if q.is_empty() {
// No more songs in queue, exit the vc // 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, Ok(stopped) => stopped,
Err(e) => { Err(e) => {
println!("Cannot stop: {:?}", e); println!("Cannot stop: {:?}", e);
@ -29,6 +30,7 @@ impl EventHandler for TrackEndNotifier {
} }
}; };
if stopped { if stopped {
music_queue::delete_queue(&self.guild_id).await;
println!("Stopped playing successfully."); println!("Stopped playing successfully.");
} else { } else {
println!("Failed to stop playing."); println!("Failed to stop playing.");
@ -40,7 +42,8 @@ impl EventHandler for TrackEndNotifier {
println!("Cannot get head of queue"); println!("Cannot get head of queue");
return None; 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; music_manager::play_song(&self.cmdctx, &self.guild_id, &head).await;
} }

View file

@ -5,11 +5,12 @@ use crate::util::user_util::get_vc_id;
use crate::HttpKey; use crate::HttpKey;
use serenity::all::{Context, CreateEmbed, GuildId, UserId}; use serenity::all::{Context, CreateEmbed, GuildId, UserId};
use songbird::error::JoinError; use songbird::error::JoinError;
use songbird::input::{Compose, YoutubeDl}; use songbird::input::YoutubeDl;
use songbird::{Event, TrackEvent}; use songbird::{Event, TrackEvent};
use std::sync::Arc; 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( pub async fn attempt_to_queue_song(
ctx: &Context, ctx: &Context,
guild_id: &GuildId, guild_id: &GuildId,
@ -18,21 +19,17 @@ pub async fn attempt_to_queue_song(
query: &str, query: &str,
) -> CreateEmbed { ) -> CreateEmbed {
if !user_util::is_user_connected_to_vc(ctx, guild_id, user_id).await { if !user_util::is_user_connected_to_vc(ctx, guild_id, user_id).await {
return Embed::create( return Embed::create_error_respose(username, "You are not connected to a VC", "Connect to my vc to start controlling the music.")
username,
"You are not connected to a VC",
"Connect to my VC to control the music.",
);
} }
let connect_to = match get_vc_id(ctx, guild_id, user_id).await { let connect_to = match get_vc_id(ctx, guild_id, user_id).await {
Some(channel_id) => channel_id, Some(channel_id) => channel_id,
None => { 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::Songbird> = &songbird::get(ctx) // TODO match
.await .await
.expect("Cannot get Songbird.") .expect("Cannot get Songbird.")
.clone(); .clone();
@ -42,23 +39,18 @@ pub async fn attempt_to_queue_song(
// self is connected to vc, check if user is in same vc // self is connected to vc, check if user is in same vc
if self_channel.is_none() { if self_channel.is_none() {
// TODO This could maybe be removed?
// Connect to VC // Connect to VC
manager manager // TODO match
.join(*guild_id, connect_to) .join(*guild_id, connect_to)
.await .await
.expect("Cannot connect>..."); .expect("Cannot connect>...");
} }
} else { } 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 // Check if user is in the same VC as the bot
if self_channel != connect_to { if self_channel != connect_to {
return Embed::create( 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.")
username,
"You are not in my VC.",
"Connect to my VC to control the music.",
);
} }
} }
@ -72,7 +64,7 @@ pub async fn attempt_to_queue_song(
}; };
// Create source // Create source
let mut src = if do_search { let src = if do_search {
YoutubeDl::new_search(http_client, query.to_string()) YoutubeDl::new_search(http_client, query.to_string())
} else { } else {
YoutubeDl::new(http_client, query.to_string()) 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; music_queue::add_to_queue(guild_id, src.clone()).await;
if currently_playing.is_some() { if currently_playing.is_some() {
// Add to queue // Add to queue
return Embed::create( return Embed::create_yt_playing(src, username, "Added to queue").await;
username,
"Added to queue",
"The song was added to the queue.",
);
} }
let _query = music_queue::get_head(guild_id) 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) { let handler_lock = match manager.get(*guild_id) {
Some(handler) => handler, Some(handler) => handler,
None => { 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 Embed::create_yt_playing(src, username, "Now playing").await
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)
} }
/// Play the provided song
pub async fn play_song(ctx: &Context, guild_id: &GuildId, target: &YoutubeDl) { pub async fn play_song(ctx: &Context, guild_id: &GuildId, target: &YoutubeDl) {
let manager = &songbird::get(ctx) // TODO match let manager = &songbird::get(ctx) // TODO match
.await .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 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 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::Songbird> = &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( pub async fn attempt_to_stop(
ctx: &Context, ctx: &Context,
guild_id: &GuildId, guild_id: &GuildId,
@ -170,11 +186,7 @@ pub async fn attempt_to_stop(
) -> CreateEmbed { ) -> CreateEmbed {
if !user_util::is_self_connected_to_vc(ctx, guild_id).await { if !user_util::is_self_connected_to_vc(ctx, guild_id).await {
// Bot is not connectd to vc; no need to dc // Bot is not connectd to vc; no need to dc
return Embed::create( return Embed::create_error_respose(username, "Bot is not connected", "And therefore there is no need to do anything.");
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) let self_channel = user_util::get_self_vc_id(ctx, guild_id)
.await .await
@ -185,48 +197,38 @@ pub async fn attempt_to_stop(
// Check if user is in the same VC as the bot // Check if user is in the same VC as the bot
if self_channel != connect_to { if self_channel != connect_to {
return Embed::create( return Embed::create_error_respose(username, "You are not in my VC.", "Connect to my VC to controll the music.");
username,
"You are not in my VC.",
"Connect to my VC to control the music.",
);
} }
let stopped = match stop(ctx, guild_id).await { let stopped = match leave(ctx, guild_id).await {
Ok(stopped) => stopped, Ok(stopped) => stopped,
Err(e) => { Err(e) => {
println!("Error while stopping: {:?}", e); println!("Error while stopping: {:?}", e);
return Embed::create( return Embed::create_error_respose(username, "There was an error", "Tell moonleay to check the logs.");
username,
"There was an error",
"Tell moonleay to check the logs.".to_string(),
);
} }
}; };
if !stopped { if !stopped {
Embed::create( 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.")
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(),
)
} else { } else {
music_queue::delete_queue(guild_id).await; // Clear queue 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. /// 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<bool, JoinError> { pub async fn leave(ctx: &Context, guild_id: &GuildId) -> Result<bool, JoinError> {
let manager = songbird::get(ctx) let manager = songbird::get(ctx)
.await .await
.expect("Cannot get Songbird") .expect("Cannot get Songbird")
.clone(); .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 { if has_handler {
handler.unwrap().lock().await.stop();
manager.remove(*guild_id).await?; manager.remove(*guild_id).await?;
return Ok(true); // Handler removed return Ok(true); // Handler removed
} }

View file

@ -6,9 +6,6 @@ use std::error::Error;
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct Config { pub struct Config {
pub discord_token: String, pub discord_token: String,
pub lavalink_address: String,
pub lavalink_password: String,
pub user_id: u64,
} }
const CONFIG_FILE: &str = "./data/config.json"; const CONFIG_FILE: &str = "./data/config.json";
@ -26,9 +23,6 @@ pub fn load() -> Result<Config, Box<dyn Error>> {
fn create_empty() -> fs::File { fn create_empty() -> fs::File {
let example_config = Config { let example_config = Config {
discord_token: "paste_your_token".to_string(), 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(); let mut config_file = fs::File::create(CONFIG_FILE).unwrap();

View file

@ -1,29 +1,56 @@
use std::fmt::Display; use std::time::Duration;
use chrono::Local; use chrono::Local;
use serenity::all::{CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter}; use serenity::all::{Color, CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter};
use songbird::input::{Compose, YoutubeDl};
pub struct Embed; pub struct Embed;
impl Embed { impl Embed {
pub fn create< pub fn create_success_response(username: &str, title: &str, desc: &str) -> CreateEmbed {
S: Into<String> + Display,
T: Into<String> + Display,
U: Into<String> + Display,
>(
username: S,
title: T,
message: U,
) -> CreateEmbed {
let current_time = Local::now().format("%Y-%m-%d @ %H:%M:%S"); let current_time = Local::now().format("%Y-%m-%d @ %H:%M:%S");
CreateEmbed::new() CreateEmbed::new()
.author(CreateEmbedAuthor::new(username.to_string())) .title(title)
.title(title) .description(desc)
.description(message) .color(Color::from_rgb(224, 49, 26))
.footer(CreateEmbedFooter::new(format!( .footer(CreateEmbedFooter::new(format!("> {} - {}", current_time, username)))
"> {} - {}", }
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)))
} }
} }