From c82fee0dd34e78c7e13f7821414c48b5e1fe09eb Mon Sep 17 00:00:00 2001 From: Benjamin Sherriff Date: Wed, 18 Dec 2024 18:32:49 -0500 Subject: [PATCH] Updated roll to notify a user --- src/bot/commands/fun/roll.rs | 167 +++++++++++++++++++---------------- src/bot/commands/mod.rs | 24 +++-- 2 files changed, 112 insertions(+), 79 deletions(-) diff --git a/src/bot/commands/fun/roll.rs b/src/bot/commands/fun/roll.rs index 7d48515..34fe946 100644 --- a/src/bot/commands/fun/roll.rs +++ b/src/bot/commands/fun/roll.rs @@ -1,34 +1,33 @@ use rand::Rng; use serenity::all::{ - CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption, + CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption, Mentionable, UserId }; -use crate::bot::commands::{create_response, create_dm, edit_response}; +use crate::bot::commands::{create_response, edit_response, user_id_dm}; pub async fn run(ctx: &Context, command: &CommandInteraction) { - let hidden = match command.data.options.get(1) { - Some(o) => match o.value.as_bool() { - Some(b) => b, - None => false, - }, - None => false, - }; - create_response(&ctx, &command, format!("Rolling..."), hidden).await; - let dice_string = match command.data.options.get(0) { - Some(o) => match o.value.as_str() { - Some(s) => s.split_whitespace().collect::(), + // Check if the roll result is private + let private = command.data.options.iter().find(|opt| opt.name == "private") + .and_then(|o| o.value.as_bool()) + .unwrap_or(true); + + // Retrieve the DM's name or ID from the options (optional) + let user = command.data.options.iter().find(|opt| opt.name == "user") + .and_then(|o| o.value.as_mentionable()); + + create_response(&ctx, &command, format!("Rolling..."), private).await; + + let dice_string = match command.data.options.get(0) + .and_then(|o| o.value.as_str()) + .map(|s| s.split_whitespace().collect::()) { + Some(dice_value) => dice_value, None => { - log::warn!("Missing dice option"); - edit_response(&ctx, &command, format!("Dice option is missing")).await; - return; + log::warn!("Missing or invalid dice option"); + let _ = edit_response(&ctx, &command, "Dice option is missing".to_string()).await; + return; } - }, - None => { - log::warn!("Missing dice option"); - edit_response(&ctx, &command, format!("Dice option is missing")).await; - return; - } - }; + }; + let dice = parse_dice(dice_string.as_str()); match dice { Ok((count, sides, modifier)) => { @@ -52,7 +51,15 @@ pub async fn run(ctx: &Context, command: &CommandInteraction) { "".to_string() } ); - edit_response(&ctx, &command, response).await; + + match user { + Some(id) => { + let user_id = UserId::new(id.get()); + user_id_dm(&ctx, &user_id, format!("Dice roll from {}: {}", &command.user.mention(), response)).await; + edit_response(&ctx, command, format!("Sending dice roll results to {}", &user_id.mention())).await; + }, + None => edit_response(&ctx, &command, response).await + }; } Err(why) => { edit_response(&ctx, &command, format!("Invalid dice string: {}", why)).await; @@ -61,64 +68,72 @@ pub async fn run(ctx: &Context, command: &CommandInteraction) { } fn parse_dice(dice: &str) -> Result<(u32, u32, i32), String> { - let mut parts = dice.split("d"); - let count = match parts.next() { - Some(c) => match c.parse::() { - Ok(n) => n, - Err(_) => return Err(format!("Invalid dice count: {}", c)), - }, - None => return Err(format!("Invalid dice string: {}", dice)), + // If the input is just a number (e.g., "20" or "6"), assume it's the number of sides + if let Ok(n) = dice.parse::() { + return Ok((1, n, 0)); // Assume 1 dice with 0 modifiers + } + + // If the input starts with "d", assume it's shorthand for "1dX" + let dice = if dice.starts_with("d") { + format!("1{}", dice) // Prepend "1" + } else { + dice.to_string() }; + + let mut parts = dice.split(['d', '+', '-'].as_ref()); let mut positive_modifier = true; - let mut parts = match parts.next() { - Some(p) => { - // Check if contains a +/- modifier - if p.contains("+") { - positive_modifier = true; - p.split("+") - } else if p.contains("-") { - positive_modifier = false; - p.split("-") - } else { - p.split("+") - } - } - None => return Err(format!("Invalid dice string: {}", dice)), - }; - let sides = match parts.next() { - Some(s) => match s.parse::() { - Ok(n) => { - if n == 4 || n == 6 || n == 8 || n == 10 || n == 12 || n == 20 || n == 100 { - n - } else { - return Err(format!( - "Expected one of d4, d6, d8, d10, d12, d20, d100 but received d{}", - s - )); - } - } - Err(_) => { - return Err(format!( - "Expected one of d4, d6, d8, d10, d12, d20, d100 but received d{}", - s - )) - } + + // Parse the dice count + let count = match parts.next() { + Some("") => 1, // Handle cases like "d6", assume 1 dice + Some(c) => match c.parse::() { + Ok(n) => n, + Err(_) => return Err(format!("Invalid dice count: {}", c)), }, None => return Err(format!("Invalid dice string: {}", dice)), }; + + // Parse the number of sides + let sides_part = parts.next().ok_or_else(|| format!("Invalid dice string: {}", dice))?; + let sides = match sides_part.parse::() { + Ok(n) => { + if [4, 6, 8, 10, 12, 20, 100].contains(&n) { + n + } else { + return Err(format!( + "Expected one of d4, d6, d8, d10, d12, d20, d100 but received d{}", + n + )); + } + } + Err(_) => return Err(format!( + "Expected one of d4, d6, d8, d10, d12, d20, d100 but received d{}", + sides_part + )), + }; + + // Determine if there's a modifier (+ or -) + if dice.contains('+') { + positive_modifier = true; + } else if dice.contains('-') { + positive_modifier = false; + } + + // Parse the modifier, if present let modifier = match parts.next() { Some(m) => match m.parse::() { - Ok(n) => { - if positive_modifier { - n - } else { - n * -1 + Ok(n) => { + if positive_modifier { + n + } else { + -n + } } - } - Err(_) => return Err(format!("Invalid dice modifier: {}", m)), + Err(_) => return Err(format!("Invalid dice modifier: {}", m)), }, - None => 0, + None => 0, // No modifier found }; + Ok((count, sides, modifier)) } @@ -131,9 +146,13 @@ pub fn register() -> CreateCommand { .add_option( CreateCommandOption::new( CommandOptionType::Boolean, - "hidden", - "Whether the roll should be hidden", + "private", + "Make the roll private (Default: True)", ) .required(false), ) + .add_option( + CreateCommandOption::new(CommandOptionType::Mentionable, "user", "User to receive the roll results") + .required(false), + ) } diff --git a/src/bot/commands/mod.rs b/src/bot/commands/mod.rs index ad549a4..c44df9b 100644 --- a/src/bot/commands/mod.rs +++ b/src/bot/commands/mod.rs @@ -1,7 +1,6 @@ use serenity::prelude::*; use serenity::all::{ - CommandInteraction, CreateInteractionResponse, CreateInteractionResponseMessage, CreateMessage, - EditInteractionResponse, InteractionResponseFlags, Message, + CommandInteraction, CreateInteractionResponse, CreateInteractionResponseMessage, CreateMessage, EditInteractionResponse, InteractionResponseFlags, Message, User, UserId }; pub mod audio; @@ -14,13 +13,28 @@ pub async fn process_message(ctx: &Context, command: &CommandInteraction, privat create_response(&ctx, &command, format!("Processing..."), private).await; } -pub async fn create_dm( +pub async fn user_id_dm( ctx: &Context, - command: &CommandInteraction, + user_id: &UserId, content: String, ) -> Option { let data = CreateMessage::new().content(content.to_owned()); - return match command.user.direct_message(ctx, data).await { + return match user_id.dm(ctx, data).await { + Ok(message) => Some(message), + Err(err) => { + log::error!("Failed to create direct message for {content}\n{err}"); + None + } + }; +} + +pub async fn user_dm( + ctx: &Context, + user: &User, + content: String, +) -> Option { + let data = CreateMessage::new().content(content.to_owned()); + return match user.direct_message(ctx, data).await { Ok(message) => Some(message), Err(err) => { log::error!("Failed to create direct message for {content}\n{err}");