-
Notifications
You must be signed in to change notification settings - Fork 0
Improve roll formulas #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
94b9ffe
dcf6f90
1a0e050
171980b
fdab1a6
82c57a6
017b05a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| -- This file should undo anything in `up.sql` | ||
| ALTER TABLE characters | ||
| DROP COLUMN saved_rolls; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| -- Your SQL goes here | ||
| ALTER TABLE characters | ||
| ADD COLUMN saved_rolls TEXT; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,7 +1,10 @@ | ||||||||||||||||
| pub mod saved_rolls; | ||||||||||||||||
| pub mod spell_sheet; | ||||||||||||||||
| pub mod stat_block; | ||||||||||||||||
| pub mod web; | ||||||||||||||||
|
|
||||||||||||||||
| use crate::common::Data; | ||||||||||||||||
|
|
||||||||||||||||
| use lazy_static::lazy_static; | ||||||||||||||||
| use poise::serenity_prelude::ButtonStyle; | ||||||||||||||||
| use poise::serenity_prelude::ChannelId; | ||||||||||||||||
|
|
@@ -11,8 +14,10 @@ use poise::serenity_prelude::CreateEmbed; | |||||||||||||||
| use poise::serenity_prelude::CreateEmbedFooter; | ||||||||||||||||
| use poise::serenity_prelude::CreateSelectMenu; | ||||||||||||||||
| use poise::serenity_prelude::CreateSelectMenuOption; | ||||||||||||||||
| use poise::serenity_prelude::EditMessage; | ||||||||||||||||
| use poise::serenity_prelude::GuildId; | ||||||||||||||||
| use poise::Command; | ||||||||||||||||
| use poise::Modal; | ||||||||||||||||
| use tokio::sync::Mutex; | ||||||||||||||||
| use tokio::sync::MutexGuard; | ||||||||||||||||
|
|
||||||||||||||||
|
|
@@ -25,20 +30,15 @@ use super::spells::SpellType; | |||||||||||||||
| use spell_sheet::SpellSheet; | ||||||||||||||||
| use stat_block::StatBlock; | ||||||||||||||||
|
|
||||||||||||||||
| use std::borrow::Cow; | ||||||||||||||||
| use std::collections::HashMap; | ||||||||||||||||
| use std::f64; | ||||||||||||||||
|
|
||||||||||||||||
| use crate::common; | ||||||||||||||||
| use crate::common::safe_to_number; | ||||||||||||||||
| use crate::common::ButtonEventSystem; | ||||||||||||||||
| use crate::common::Context; | ||||||||||||||||
| use crate::common::Error; | ||||||||||||||||
| use crate::db; | ||||||||||||||||
| use crate::db::models::Character; | ||||||||||||||||
| use crate::db::DbError; | ||||||||||||||||
|
|
||||||||||||||||
| use diesel::SqliteConnection; | ||||||||||||||||
|
|
||||||||||||||||
| use super::get_user_character; | ||||||||||||||||
| use super::CharacterSheetable; | ||||||||||||||||
|
|
@@ -65,6 +65,8 @@ use event_handlers::UpdateStatusEventParams; | |||||||||||||||
| use event_handlers::ChangeManaEvent; | ||||||||||||||||
| use event_handlers::ChangeManaEventParams; | ||||||||||||||||
|
|
||||||||||||||||
| type ApplicationContext<'a> = poise::ApplicationContext<'a, Data, Error>; | ||||||||||||||||
|
|
||||||||||||||||
| pub fn register_events(event_system: &mut MutexGuard<ButtonEventSystem>) { | ||||||||||||||||
| event_system.register_handler(RollEvent); | ||||||||||||||||
| event_system.register_handler(ChangeCharacterEvent); | ||||||||||||||||
|
|
@@ -105,6 +107,76 @@ pub async fn pull_spellsheet(ctx: Context<'_>) -> Result<(), Error> { | |||||||||||||||
| Ok(()) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| #[derive(Debug, Modal)] | ||||||||||||||||
| #[name = "Edit Message"] // Struct name by default | ||||||||||||||||
| struct EditMessageModal { | ||||||||||||||||
| #[name = "Message content:"] | ||||||||||||||||
| #[paragraph] | ||||||||||||||||
| message: String, // Option means optional input | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| #[derive(Debug, Modal)] | ||||||||||||||||
| #[name = "Edit Saved Rolls"] // Struct name by default | ||||||||||||||||
| struct EditSavedRollsModal { | ||||||||||||||||
| #[name = "Saved Rolls"] | ||||||||||||||||
| #[paragraph] | ||||||||||||||||
| message: Option<String>, // Option means optional input | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| #[poise::command(slash_command)] | ||||||||||||||||
| pub async fn edit_saved_rolls(ctx: ApplicationContext<'_>) -> Result<(), Error> { | ||||||||||||||||
| let author = &ctx.author(); | ||||||||||||||||
|
|
||||||||||||||||
| let user_id = author.id.get(); | ||||||||||||||||
|
|
||||||||||||||||
| let user = db::users::get_or_create(user_id)?; | ||||||||||||||||
|
|
||||||||||||||||
| let mut char = if let Some(character_id) = user.selected_character { | ||||||||||||||||
| Some(db::characters::get(character_id)?) | ||||||||||||||||
| } else { | ||||||||||||||||
| None | ||||||||||||||||
| } | ||||||||||||||||
| .ok_or(RpgError::NoCharacterSelected)?; | ||||||||||||||||
|
|
||||||||||||||||
| // let char = get_user_character(ctx.serenity_context()); | ||||||||||||||||
|
|
||||||||||||||||
| let message_modal = EditSavedRollsModal { | ||||||||||||||||
| message: char.saved_rolls, | ||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| let data = Modal::execute_with_defaults(ctx, message_modal).await?; | ||||||||||||||||
| if let Some(data) = data { | ||||||||||||||||
| char.saved_rolls = data.message; | ||||||||||||||||
| db::characters::update(&char)?; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| Ok(()) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // #[poise::command(context_menu_command = "Edit message")] | ||||||||||||||||
| // pub async fn edit_character( | ||||||||||||||||
| // ctx: ApplicationContext<'_>, | ||||||||||||||||
|
|
||||||||||||||||
| // msg: crate::serenity::Message, | ||||||||||||||||
| // ) -> Result<(), Error> { | ||||||||||||||||
| // let content = &msg.content; | ||||||||||||||||
|
|
||||||||||||||||
| // let message_modal = EditMessageModal { | ||||||||||||||||
| // message: content.clone(), | ||||||||||||||||
| // }; | ||||||||||||||||
|
|
||||||||||||||||
| // let data = Modal::execute_with_defaults(ctx, message_modal).await?; | ||||||||||||||||
| // if let Some(data) = data { | ||||||||||||||||
| // if &data.message != content { | ||||||||||||||||
| // msg.clone() | ||||||||||||||||
| // .edit(ctx, EditMessage::default().content(&data.message)) | ||||||||||||||||
| // .await?; | ||||||||||||||||
| // } | ||||||||||||||||
| // } | ||||||||||||||||
|
|
||||||||||||||||
| // Ok(()) | ||||||||||||||||
| // } | ||||||||||||||||
|
|
||||||||||||||||
| static BAR_LENGTH: i32 = 16; | ||||||||||||||||
|
|
||||||||||||||||
| pub async fn generate_status_embed( | ||||||||||||||||
|
|
@@ -1143,6 +1215,18 @@ pub async fn roll_with_char_sheet( | |||||||||||||||
|
|
||||||||||||||||
| match stat_block_result { | ||||||||||||||||
| Ok(stat_block) => { | ||||||||||||||||
| if let Some(custom_rolls) = &character.saved_rolls { | ||||||||||||||||
| let custom_roll_map: std::collections::HashMap<_, _> = custom_rolls | ||||||||||||||||
| .lines() | ||||||||||||||||
| .filter_map(|line| line.split_once(':')) | ||||||||||||||||
| .map(|(k, v)| (k.trim(), v.trim())) | ||||||||||||||||
| .collect(); | ||||||||||||||||
|
|
||||||||||||||||
| for (key, value) in &custom_roll_map { | ||||||||||||||||
| str_replaced = str_replaced.replace(key, &value.to_string()); | ||||||||||||||||
| } | ||||||||||||||||
flashgnash marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| if let Some(stats_object) = stat_block | ||||||||||||||||
| .stats | ||||||||||||||||
| .as_ref() | ||||||||||||||||
|
|
@@ -1164,6 +1248,19 @@ pub async fn roll_with_char_sheet( | |||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| if let Some(special_stats_object) = stat_block | ||||||||||||||||
| .special_stats | ||||||||||||||||
| .as_ref() | ||||||||||||||||
| .and_then(|special_stats| special_stats.as_object()) | ||||||||||||||||
| { | ||||||||||||||||
| println!("Testing"); | ||||||||||||||||
| for (special_stat, value) in special_stats_object { | ||||||||||||||||
| str_replaced = str_replaced.replace(special_stat, &value.to_string()); | ||||||||||||||||
| println!("special stat replaced: {special_stat}: {value}") | ||||||||||||||||
| } | ||||||||||||||||
|
Comment on lines
+1255
to
+1260
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2 | Confidence: High Debug print statements left in production code create noise in logs and expose potentially sensitive roll calculation details. These should be removed or gated behind proper logging levels.
Suggested change
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if you're able to reply str_replaced is used later on, you're proposing removing the entire for loop and cutting out like 1/3 of the new feature |
||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| println!("{}", str_replaced); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| Err(e) => { | ||||||||||||||||
|
|
@@ -1367,6 +1464,8 @@ pub async fn create_character( | |||||||||||||||
| user_id: Some(user_id.to_string()), | ||||||||||||||||
| roll_server_id: roll_server_id, | ||||||||||||||||
|
|
||||||||||||||||
| saved_rolls: None, | ||||||||||||||||
|
|
||||||||||||||||
| stat_block: None, | ||||||||||||||||
| stat_block_hash: None, | ||||||||||||||||
|
|
||||||||||||||||
|
|
@@ -1688,12 +1787,16 @@ pub async fn pull_stats(ctx: Context<'_>) -> Result<(), Error> { | |||||||||||||||
| .await? | ||||||||||||||||
| .ok_or(RpgError::NoCharacterSheet)?; | ||||||||||||||||
|
|
||||||||||||||||
| let reply = CreateReply::default().content( | ||||||||||||||||
| stat_block | ||||||||||||||||
| .sheet_info | ||||||||||||||||
| .jsonified_message | ||||||||||||||||
| .expect("Stat block should always generate json"), | ||||||||||||||||
| ); | ||||||||||||||||
| let json = stat_block | ||||||||||||||||
| .sheet_info | ||||||||||||||||
| .jsonified_message | ||||||||||||||||
| .expect("Stat block should always generate json"); | ||||||||||||||||
|
|
||||||||||||||||
| let pretty_json = | ||||||||||||||||
| serde_json::to_string_pretty(&serde_json::from_str::<serde_json::Value>(&json).unwrap()) | ||||||||||||||||
| .unwrap(); | ||||||||||||||||
|
Comment on lines
+1795
to
+1797
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1 | Confidence: High Double-unwrap chain creates potential panic points. If the original JSON is invalid or pretty-printing fails, the command will crash. This is particularly risky since it involves user-generated content from character sheets. Code Suggestion: let pretty_json = match serde_json::from_str::<serde_json::Value>(&json) {
Ok(value) => serde_json::to_string_pretty(&value).unwrap_or(json),
Err(_) => json,
}; |
||||||||||||||||
|
|
||||||||||||||||
| let reply = CreateReply::default().content(pretty_json); | ||||||||||||||||
| msg.edit(ctx, reply).await?; | ||||||||||||||||
|
|
||||||||||||||||
| return Ok(()); | ||||||||||||||||
|
|
@@ -1808,5 +1911,7 @@ pub fn commands() -> Vec<Command<crate::common::Data, crate::common::Error>> { | |||||||||||||||
| set_spells(), | ||||||||||||||||
| level_up(), | ||||||||||||||||
| roll(), | ||||||||||||||||
| // edit_character(), | ||||||||||||||||
| edit_saved_rolls(), | ||||||||||||||||
| ]; | ||||||||||||||||
| } | ||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| // use std::fmt; | ||
|
|
||
| // use crate::common::Error; | ||
|
|
||
| // use crate::db::models::Character; | ||
|
|
||
| // use super::super::CharacterSheetable; | ||
| // use super::super::RpgError; | ||
| // use super::super::SheetInfo; | ||
|
|
||
| // use poise::serenity_prelude::Message; | ||
|
|
||
| // pub struct SavedRollSheet { | ||
| // pub sheet_info: SheetInfo, | ||
| // pub character_id: Option<i32>, | ||
| // pub saved_rolls: Option<Vec<SavedRoll>>, | ||
| // } | ||
|
|
||
| // #[derive(Clone)] | ||
| // pub struct SavedRoll { | ||
| // pub name: String, | ||
| // pub formula: String, | ||
| // } | ||
|
|
||
| // impl fmt::Display for SavedRollSheet { | ||
| // fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| // // if let (Some(name), Some(formula)) = (&self.name, &self.formula) {} | ||
|
|
||
| // write!(f, "I can't be bothered to do this right now") | ||
| // } | ||
| // } | ||
|
|
||
| // impl CharacterSheetable for SavedRollSheet { | ||
| // fn new() -> Self { | ||
| // return Self { | ||
| // character_id: None, | ||
|
|
||
| // saved_rolls: None, | ||
|
|
||
| // sheet_info: SheetInfo { | ||
| // original_message: None, | ||
| // jsonified_message: None, | ||
| // message_hash: None, | ||
| // changed: false, | ||
| // character: None, | ||
| // deserialized_message: None, | ||
| // }, | ||
| // }; | ||
| // } | ||
|
|
||
| // fn post_init(&mut self) -> Result<(), Error> { | ||
| // let deserialized_message = self | ||
| // .sheet_info | ||
| // .deserialized_message | ||
| // .as_ref() | ||
| // .expect("This should be set before calling post_init"); | ||
|
|
||
| // Ok(()) | ||
| // } | ||
|
|
||
| // fn update_character(&mut self) { | ||
| // let mut char = self | ||
| // .sheet_info | ||
| // .character | ||
| // .clone() | ||
| // .unwrap_or(Character::new_empty()); | ||
|
|
||
| // char.stat_block = Some( | ||
| // self.sheet_info | ||
| // .jsonified_message | ||
| // .clone() | ||
| // .expect("Character sheet should always generate jsonified message"), | ||
| // ); | ||
|
|
||
| // char.stat_block_hash = self.sheet_info.message_hash.clone(); | ||
|
|
||
| // self.sheet_info.character = Some(char); | ||
| // } | ||
|
|
||
| // fn mut_sheet_info(&mut self) -> &mut SheetInfo { | ||
| // &mut self.sheet_info | ||
| // } | ||
| // fn sheet_info(&self) -> &SheetInfo { | ||
| // &self.sheet_info | ||
| // } | ||
|
|
||
| // fn get_previous_block(character: &Character) -> (Option<String>, Option<String>) { | ||
| // return ( | ||
| // character.stat_block_hash.clone(), | ||
| // character.stat_block.clone(), | ||
| // ); | ||
| // } | ||
|
|
||
| // async fn get_sheet_message( | ||
| // ctx: &poise::serenity_prelude::Context, | ||
| // character: &Character, | ||
| // ) -> Result<Message, Error> { | ||
| // if let (Some(channel_id_u64), Some(message_id_u64)) = ( | ||
| // character.stat_block_channel_id.clone(), | ||
| // character.stat_block_message_id.clone(), | ||
| // ) { | ||
| // let channel_id = channel_id_u64.parse().expect("Invalid channel ID"); | ||
| // let message_id = message_id_u64.parse().expect("Invalid message ID"); | ||
|
|
||
| // let message = crate::common::fetch_message(&ctx, channel_id, message_id).await?; | ||
|
|
||
| // return Ok(message); | ||
| // } | ||
|
|
||
| // Err(Box::new(RpgError::NoCharacterSheet)) | ||
| // } | ||
|
|
||
| // const PROMPT: &'static str = r#" | ||
| // You are a saved roll reading program. | ||
| // Following this prompt you will receive a key value pair list of roll formulas and their names. | ||
| // Use the following schema: | ||
| // { | ||
|
|
||
| // "my_custom_roll": (string), | ||
| // "my_other_roll": (string) | ||
|
|
||
| // } | ||
|
|
||
| // All keys should be lower case and spell corrected. Respond with only valid, minified json | ||
|
|
||
| // DO NOT USE BACKTICKS OR BACKSLASHES IN YOUR RESPONSE | ||
|
|
||
| // "#; | ||
| // } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2 | Confidence: Medium
The modal uses the raw database value without any validation or sanitization. If the saved_rolls contain malformed data (e.g., lines without colons), the editing interface will display corrupted content, and the roll command will silently ignore those lines.
Code Suggestion: