From 3ca91c776555b61ce6fb57b789f0763140532cc7 Mon Sep 17 00:00:00 2001 From: Benjamin Sherriff Date: Fri, 6 Oct 2023 23:34:25 -0400 Subject: [PATCH] Changing bot commands to be used by endpoints --- .../migrations/000010_create_guilds/down.sql | 1 + .../migrations/000010_create_guilds/up.sql | 5 ++ service/src/bot/commands/audio/mod.rs | 86 ++----------------- service/src/bot/commands/audio/play.rs | 56 ++++++++++-- service/src/bot/commands/audio/volume.rs | 13 +-- service/src/bot/handler.rs | 13 +-- service/src/db/guilds/mod.rs | 3 + service/src/db/guilds/model.rs | 43 ++++++++++ service/src/db/mod.rs | 1 + service/src/db/schema.rs | 8 ++ service/src/main.rs | 29 ++----- 11 files changed, 133 insertions(+), 125 deletions(-) create mode 100644 service/migrations/000010_create_guilds/down.sql create mode 100644 service/migrations/000010_create_guilds/up.sql create mode 100644 service/src/db/guilds/mod.rs create mode 100644 service/src/db/guilds/model.rs diff --git a/service/migrations/000010_create_guilds/down.sql b/service/migrations/000010_create_guilds/down.sql new file mode 100644 index 0000000..c2477fc --- /dev/null +++ b/service/migrations/000010_create_guilds/down.sql @@ -0,0 +1 @@ +DROP TABLE guilds; \ No newline at end of file diff --git a/service/migrations/000010_create_guilds/up.sql b/service/migrations/000010_create_guilds/up.sql new file mode 100644 index 0000000..54c825d --- /dev/null +++ b/service/migrations/000010_create_guilds/up.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS guilds ( + id BIGINT PRIMARY KEY NOT NULL, + name TEXT NOT NULL, + volume INTEGER NOT NULL +); \ No newline at end of file diff --git a/service/src/bot/commands/audio/mod.rs b/service/src/bot/commands/audio/mod.rs index cd60d28..4eaeb76 100644 --- a/service/src/bot/commands/audio/mod.rs +++ b/service/src/bot/commands/audio/mod.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use log::debug; +use serenity::client::Cache; use serenity::model::application::interaction::{InteractionResponseType, application_command::ApplicationCommandInteraction}; use serenity::model::prelude::{GuildId, ChannelId}; use serenity::model::user::User; @@ -29,16 +30,7 @@ pub struct AudioConfig { pub volume: f32 } -/// Joins a Discord voice channel. -/// -/// # Arguments -/// - ctx - The context of the command. -/// - guild_id_option - The guild ID of the guild to join. -/// - user - The user that is requesting to join the voice channel. -/// -/// # Returns -/// Result<(), String> - Ok if the bot successfully joined the voice channel, Err if there was an error. -pub async fn join(ctx: &Context, guild_id_option: &Option, user: &User) -> Result<(), String> { +pub async fn join(cache: &Arc, manager: Arc, guild_id_option: &Option, user: &User) -> Result<(), String> { let guild_id = match guild_id_option { Some(g) => g, None => { @@ -46,13 +38,12 @@ pub async fn join(ctx: &Context, guild_id_option: &Option, user: &User) } }; - let channel_id = match find_voice_channel(&ctx, &guild_id, &user) { + let channel_id = match find_voice_channel(cache, &guild_id, &user) { Ok(channel) => channel, Err(err) => return Err(format!("{}", err)) }; debug!("<{}> Joining channel {}", guild_id.0, channel_id); - let manager = get_songbird(ctx).await; let (_handle_lock, success) = manager.join(guild_id.to_owned(), channel_id.to_owned()).await; match success { Ok(s) => Ok(s), @@ -60,15 +51,7 @@ pub async fn join(ctx: &Context, guild_id_option: &Option, user: &User) } } -/// Leaves a Discord voice channel. -/// -/// # Arguments -/// - ctx - The context of the command. -/// - guild_id_option - The guild ID of the guild to leave. -/// -/// # Returns -/// Result<(), String> - Ok if the bot successfully left the voice channel, Err if there was an error. -pub async fn leave(ctx: &Context, guild_id_option: &Option) -> Result<(), String> { +pub async fn leave(manager: Arc, guild_id_option: &Option) -> Result<(), String> { let guild_id = match guild_id_option { Some(g) => g, None => { @@ -76,7 +59,6 @@ pub async fn leave(ctx: &Context, guild_id_option: &Option) -> Result<( } }; - let manager = get_songbird(ctx).await; if manager.get(*guild_id).is_some() { debug!("<{}> Disconnecting from channel", guild_id.0); if let Err(e) = manager.remove(*guild_id).await { @@ -86,17 +68,8 @@ pub async fn leave(ctx: &Context, guild_id_option: &Option) -> Result<( Ok(()) } -/// Finds the voice channel that the user is in. -/// -/// # Arguments -/// - ctx - The context of the command. -/// - guild_id - The guild ID of the guild to search. -/// - user - The user to search for. -/// -/// # Returns -/// Result - Ok if the user is in a voice channel, Err if the user is not in a voice channel. -fn find_voice_channel(ctx: &Context, guild_id: &GuildId, user: &User) -> Result { - let guild = match guild_id.to_guild_cached(ctx.cache.to_owned()) { +fn find_voice_channel(cache: &Arc, guild_id: &GuildId, user: &User) -> Result { + let guild = match guild_id.to_guild_cached(cache.to_owned()) { Some(g) => g, None => return Err(format!("Guild not found")) }; @@ -107,15 +80,6 @@ fn find_voice_channel(ctx: &Context, guild_id: &GuildId, user: &User) -> Result< } } -/// Creates a response to an interaction. -/// -/// # Arguments -/// - ctx - The context of the command. -/// - command - The command that was sent. -/// - content - The content of the response. -/// -/// # Returns -/// Result<(), SerenityError> - Ok if the response was created successfully, Err if there was an error. pub async fn create_response(ctx: &Context, command: &ApplicationCommandInteraction, content: String) -> Result<(), SerenityError> { command.create_interaction_response(&ctx.http, |response: &mut serenity::builder::CreateInteractionResponse<'_>| { response @@ -124,31 +88,13 @@ pub async fn create_response(ctx: &Context, command: &ApplicationCommandInteract }).await } -/// Edits a response to an interaction. -/// -/// # Arguments -/// - ctx - The context of the command. -/// - command - The command that was sent. -/// - content - The content of the response. -/// -/// # Returns -/// Result - Ok if the response was edited successfully, Err if there was an error. pub async fn edit_response(ctx: &Context, command: &ApplicationCommandInteraction, content: String) -> Result { command.edit_original_interaction_response(&ctx.http, |response: &mut serenity::builder::EditInteractionResponse| { response.content(content) }).await } -/// Adds a song to the queue. -/// -/// # Arguments -/// - call - The call to add the song to. -/// - url - The URL of the song to add. -/// - lazy - Whether or not to lazy load the song. -/// -/// # Returns -/// Result - Ok if the song was added successfully, Err if there was an error. -pub async fn add_song(call: Arc>, url: &str, lazy: bool, audio_config: Option<&AudioConfig>) -> Result { +pub async fn add_song(call: Arc>, url: &str, lazy: bool, volume: Option) -> Result { let source = if is_valid_url(url) { Restartable::ytdl(url.to_owned(), lazy).await? } else { @@ -158,19 +104,12 @@ pub async fn add_song(call: Arc>, url: &str, lazy: bool, audio_confi let track: Input = source.into(); let metadata = *track.metadata.clone(); let track_handle = handler.enqueue_source(track); - if let Some(ac) = audio_config { - let _ = track_handle.set_volume(ac.volume); + if let Some(volume) = volume { + let _ = track_handle.set_volume(volume); } Ok(metadata) } -/// Checks if a string is a valid URL. -/// -/// # Arguments -/// - url - The string to check. -/// -/// # Returns -/// bool - True if the string is a valid URL, false if it is not. fn is_valid_url(url: &str) -> bool { match url.parse::() { Ok(_) => return true, @@ -178,13 +117,6 @@ fn is_valid_url(url: &str) -> bool { } } -/// Gets the Songbird voice client. -/// -/// # Arguments -/// - ctx - The context of the command. -/// -/// # Returns -/// Arc - The Songbird voice client. pub async fn get_songbird(ctx: &Context) -> Arc { songbird::get(ctx).await.expect("Songbird Voice client placed in at initialization") } diff --git a/service/src/bot/commands/audio/play.rs b/service/src/bot/commands/audio/play.rs index cb0442b..164b40e 100644 --- a/service/src/bot/commands/audio/play.rs +++ b/service/src/bot/commands/audio/play.rs @@ -1,11 +1,18 @@ +use std::sync::Arc; + use log::{debug, warn, error}; +use serenity::model::prelude::GuildId; +use serenity::model::user::User; use serenity::{prelude::*, async_trait}; use serenity::builder::CreateApplicationCommand; use serenity::model::application::interaction::application_command::ApplicationCommandInteraction; +use siren::ServiceError; use songbird::EventHandler; -use crate::bot::commands::audio::{join, leave, add_song, get_songbird, AudioConfigs}; +use crate::AppState; +use crate::bot::commands::audio::{join, leave, add_song, get_songbird}; +use crate::db::guilds::QueryGuild; use super::{create_response, edit_response}; @@ -46,7 +53,8 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { return; } - match join(&ctx, &command.guild_id, &command.user).await { + let manager = get_songbird(ctx).await; + match join(&ctx.cache, manager,&command.guild_id, &command.user).await { Ok(_) => { let guild_id = match command.guild_id { Some(g) => g, @@ -65,12 +73,8 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { let call_handler = handler_lock.lock().await; call_handler.queue().is_empty() }; - let audio_config = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected AudioConfigs in TypeMap.").clone() - }; - let ac = audio_config.read().await; - match add_song(handler_lock.clone(), &track_url, is_queue_empty, ac.get(&guild_id)).await { + let guild = QueryGuild::get(guild_id.0 as i64).unwrap(); + match add_song(handler_lock.clone(), &track_url, is_queue_empty, Some(guild.volume)).await { Ok(added_song) => { let track_title = added_song.title.unwrap(); debug!("Added track: {}", track_title); @@ -86,7 +90,7 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { if let Err(why) = edit_response(&ctx, &command, format!("Failed to add song: {}", why)).await { error!("Failed to edit response message: {}", why); } - if let Err(why) = leave(&ctx, &command.guild_id).await { + if let Err(why) = leave(manager, &command.guild_id).await { error!("Failed to leave voice channel: {}", why); } return; @@ -103,6 +107,40 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { } } +pub async fn play(state: Arc, guild_id: Option, user: &User, track_url: String) -> Result<(), ServiceError> { + match join(&state.cache, Arc::clone(&state.songbird), &guild_id, user).await { + Ok(_) => { + let guild_id = match guild_id { + Some(g) => g, + None => { + return Err(ServiceError { + status: 422, + message: "No guild ID set".to_string() + }); + } + }; + if let Some(handler_lock) = state.songbird.get(guild_id) { + let is_queue_empty = { + let call_handler = handler_lock.lock().await; + call_handler.queue().is_empty() + }; + let guild = QueryGuild::get(guild_id.0 as i64)?; + match add_song(handler_lock.clone(), &track_url, is_queue_empty, Some(guild.volume)).await { + Ok(_) => {}, + Err(_) => {} + } + } + Ok(()) + }, + Err(err) => { + return Err(ServiceError { + status: 422, + message: err.to_string() + }); + } + } +} + pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command.name("play").description("Plays the given track").create_option(|option| { option .name("track") diff --git a/service/src/bot/commands/audio/volume.rs b/service/src/bot/commands/audio/volume.rs index 4ef8501..a8cf258 100644 --- a/service/src/bot/commands/audio/volume.rs +++ b/service/src/bot/commands/audio/volume.rs @@ -4,7 +4,9 @@ use serenity::prelude::*; use serenity::builder::CreateApplicationCommand; use serenity::model::application::interaction::application_command::ApplicationCommandInteraction; -use super::{get_songbird, create_response, edit_response, AudioConfigs, AudioConfig}; +use crate::db::guilds::InsertGuild; + +use super::{get_songbird, create_response, edit_response}; pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { // Get the volume @@ -55,14 +57,7 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { return; } }; - let audio_config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected AudioConfigs in TypeMap.").clone() - }; - { - let mut audio_configs = audio_config_lock.write().await; - *audio_configs.entry(guild_id).or_insert(AudioConfig { volume: 1.0 }) = AudioConfig { volume: bound_volume }; - } + let _ = InsertGuild::update_audio(guild_id.0 as i64, bound_volume); let manager = get_songbird(ctx).await; if let Some(handler_lock) = manager.get(guild_id) { let handler = handler_lock.lock().await; diff --git a/service/src/bot/handler.rs b/service/src/bot/handler.rs index 893eaf8..848ebdb 100644 --- a/service/src/bot/handler.rs +++ b/service/src/bot/handler.rs @@ -5,8 +5,10 @@ use serenity::model::gateway::Ready; use serenity::model::channel::Message; use serenity::prelude::*; +use crate::db::guilds::InsertGuild; + use super::commands; -use super::commands::audio::{AudioConfigs, create_response, AudioConfig}; +use super::commands::audio::create_response; pub struct Handler { // Open AI Config @@ -72,14 +74,7 @@ impl EventHandler for Handler { warn!("No ready guilds found"); } for guild in ready.guilds { - let audio_config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected AudioConfigs in TypeMap.").clone() - }; - { - let mut audio_configs = audio_config_lock.write().await; - let _ = audio_configs.insert(guild.id, AudioConfig { volume: 1.0 }); - } + let _ = InsertGuild::insert(InsertGuild { id: (guild.id.0 as i64), name: "".to_string(), volume: 100.0 }); let commands = guild.id.set_application_commands(&ctx.http, |commands| { commands.create_application_command(|command: &mut serenity::builder::CreateApplicationCommand| { commands::ping::register(command) }) .create_application_command(|command: &mut serenity::builder::CreateApplicationCommand| { commands::audio::play::register(command) }) diff --git a/service/src/db/guilds/mod.rs b/service/src/db/guilds/mod.rs new file mode 100644 index 0000000..24e3024 --- /dev/null +++ b/service/src/db/guilds/mod.rs @@ -0,0 +1,3 @@ +mod model; + +pub use model::*; \ No newline at end of file diff --git a/service/src/db/guilds/model.rs b/service/src/db/guilds/model.rs new file mode 100644 index 0000000..4017cab --- /dev/null +++ b/service/src/db/guilds/model.rs @@ -0,0 +1,43 @@ +use diesel::prelude::*; +use serde::{Serialize, Deserialize}; +use siren::ServiceError; + +use crate::db::{schema::guilds, connection}; + +#[derive(Queryable, QueryableByName, Serialize, Deserialize)] +#[diesel(table_name = guilds)] +pub struct QueryGuild { + pub id: i64, + pub name: String, + pub volume: f32 +} + +impl QueryGuild { + pub fn get(id: i64) -> Result { + let mut conn = connection()?; + let guild = guilds::table.filter(guilds::id.eq(id)).first(&mut conn)?; + Ok(guild) + } +} + +#[derive(Insertable, AsChangeset, Serialize, Deserialize)] +#[diesel(table_name = guilds)] +pub struct InsertGuild { + pub id: i64, + pub name: String, + pub volume: f32 +} + +impl InsertGuild { + pub fn insert(guild: Self) -> Result { + let mut conn = connection()?; + let guild = diesel::insert_into(guilds::table).values(guild).get_result(&mut conn)?; + Ok(guild) + } + + pub fn update_audio(id: i64, volume: f32) -> Result { + let mut conn = connection()?; + let guild = diesel::update(guilds::table.filter(guilds::id.eq(id))).set(guilds::volume.eq(volume)).get_result(&mut conn)?; + Ok(guild) + } +} diff --git a/service/src/db/mod.rs b/service/src/db/mod.rs index 4b69032..d444418 100644 --- a/service/src/db/mod.rs +++ b/service/src/db/mod.rs @@ -11,6 +11,7 @@ pub mod bestiary; pub mod classes; pub mod conditions; pub mod feats; +pub mod guilds; pub mod items; pub mod messages; pub mod options; diff --git a/service/src/db/schema.rs b/service/src/db/schema.rs index a148271..2564bdc 100644 --- a/service/src/db/schema.rs +++ b/service/src/db/schema.rs @@ -29,4 +29,12 @@ diesel::table! { attack_type -> Nullable, data -> Jsonb } +} + +diesel::table! { + guilds (id) { + id -> BigInt, + name -> Text, + volume -> Float, + } } \ No newline at end of file diff --git a/service/src/main.rs b/service/src/main.rs index c0fe250..200bf01 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -3,19 +3,18 @@ extern crate diesel; extern crate diesel_migrations; use std::env; -use std::collections::{HashSet, HashMap}; +use std::collections::HashSet; use std::sync::Arc; -use bot::commands::audio::AudioConfig; use log::{error, warn, info}; +use serenity::client::Cache; use serenity::framework::StandardFramework; use serenity::http::Http; -use serenity::model::prelude::GuildId; use serenity::prelude::*; use songbird::{SerenityInit, Songbird}; use actix_cors::Cors; use actix_web::{HttpServer, App, web}; -use crate::bot::{commands::{oai::GPTModel, audio::AudioConfigs}, handler::Handler}; +use crate::bot::{commands::oai::GPTModel, handler::Handler}; use dotenv::dotenv; @@ -74,35 +73,23 @@ async fn main() -> std::io::Result<()> { } }; - // let songbird = Songbird::serenity_from_config(songbird::Config::default().decode_mode(songbird::driver::DecodeMode::Decode)); let songbird = Songbird::serenity(); let mut client = Client::builder(token, intents) .event_handler(handler) .framework(StandardFramework::new() .configure(|c| c.owners(owners))) - // .register_songbird_with(Arc::clone(&songbird)) - // .register_songbird_from_config(songbird::Config::default().decode_mode(songbird::driver::DecodeMode::Decode)) .register_songbird() .await .expect("Error creating client"); - let audio_configs: Arc>> = Arc::new(RwLock::new(HashMap::default())); - - { - let mut data = client.data.write().await; - data.insert::(Arc::clone(&audio_configs)); - } - let http = Arc::clone(&client.cache_and_http.http); - // let cache_http = Arc::clone(&client.cache_and_http.clone()); - // let data = Arc::clone(&client.data.clone()); - // let t = songbird::Config::default().decode_mode(songbird::driver::DecodeMode::Decode); + let cache = Arc::clone(&client.cache_and_http.cache); let app_data = Arc::new(AppState { http, - songbird: Arc::clone(&songbird), - audio_configs + cache, + songbird: Arc::clone(&songbird) }); @@ -153,6 +140,6 @@ async fn main() -> std::io::Result<()> { pub struct AppState { pub http: Arc, - pub songbird: Arc, - pub audio_configs: Arc>> + pub cache: Arc, + pub songbird: Arc }