diff --git a/Cargo.lock b/Cargo.lock index 4469bdf..1021b64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1093,7 +1093,7 @@ dependencies = [ [[package]] name = "fren" -version = "1.8.0" +version = "1.9.0" dependencies = [ "axum 0.8.1", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index 0133ca7..ab0aa80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fren" -version = "1.8.0" +version = "1.9.0" edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/config.rs b/src/config.rs index 1711f4f..1d1e591 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,7 +3,7 @@ use crate::error::Error; use crate::migrations::{CURRENT_DB_VERSION, do_migration}; use config::{Config, File}; use cta_api::CTAClient; -use j_db::database::Database; +use j_db::database::{DB_METADATA_ID, Database}; use j_db::metadata::DBMetadata; use poise::serenity_prelude::model::id::ChannelId; use poise::serenity_prelude::model::prelude::{GuildId, UserId}; @@ -90,10 +90,9 @@ impl GlobalData { if version == 0 { db.insert::(DBMetadata { - id: None, + id: Some(DB_METADATA_ID), version: CURRENT_DB_VERSION, - }) - .unwrap(); + })?; } do_migration(&db); diff --git a/src/discord/admin.rs b/src/discord/admin.rs index bb409e9..ff451e8 100644 --- a/src/discord/admin.rs +++ b/src/discord/admin.rs @@ -8,6 +8,7 @@ use crate::models::lil_fren::{ }; use crate::models::managed_roles::ManagedRole; use crate::models::movie::Movie; +use crate::models::random::{Random, RandomConfig}; use crate::models::social_credit::SocialCreditPhrase; use crate::models::task::Task; use crate::user::User; @@ -351,3 +352,44 @@ pub async fn remove_movie( .await?; Ok(()) } + +/// Reset Random Scores +#[poise::command(prefix_command, category = "Admin", check = "is_admin")] +pub async fn reset_random_score( + ctx: Context<'_>, + #[description = "Random to reset"] random: String, +) -> Result<(), Error> { + RandomConfig::reset_scores(&ctx.data().db, &random)?; + + ctx.reply( + "Scores have been reset, hope you still like getting the same response 5 times in a row!", + ) + .await?; + + Ok(()) +} + +/// Reset Random Scores +#[poise::command(prefix_command, category = "Admin", check = "is_admin")] +pub async fn remove_random_result( + ctx: Context<'_>, + #[description = "ID of the random to remove"] random_id: u64, +) -> Result<(), Error> { + let resp = ctx.data().db.remove::(random_id)?; + + ctx.data() + .db + .filter(|_, _: &RandomConfig| true)? + .for_each(|mut random: RandomConfig| { + if random.responses.remove(&resp.id().unwrap()) { + ctx.data().db.insert(random).ok(); + } + }); + + let mut msg_builder = MessageBuilder::new(); + msg_builder.push_line("The following random has been removed: "); + msg_builder.push_codeblock_safe(resp.response, None); + ctx.reply(msg_builder.build()).await?; + + Ok(()) +} diff --git a/src/discord/joke.rs b/src/discord/joke.rs index 77d50ca..ecf1803 100644 --- a/src/discord/joke.rs +++ b/src/discord/joke.rs @@ -156,7 +156,7 @@ pub async fn add_random( Ok(()) } -/// List all of the random sets +/// List all the responses from a random set #[poise::command(prefix_command, category = "Joke")] pub async fn list_random( ctx: Context<'_>, @@ -197,6 +197,28 @@ pub async fn list_random( Ok(()) } +/// List all random sets +#[poise::command(prefix_command, category = "Joke")] +pub async fn list_randoms(ctx: Context<'_>) -> Result<(), Error> { + let randoms: Vec = ctx + .data() + .db + .filter(|_, _random: &RandomConfig| true)? + .collect(); + + let mut msg_build = MessageBuilder::new(); + + msg_build.push_line("List of all randoms:"); + + for random in &randoms { + msg_build.push_line(format!("* {}", random.name)); + } + + ctx.reply(msg_build.build()).await?; + + Ok(()) +} + /// Roll a totally not loaded digital die! #[poise::command(prefix_command, category = "Joke")] pub async fn roll( diff --git a/src/discord/mod.rs b/src/discord/mod.rs index 2f82f60..e21c71c 100644 --- a/src/discord/mod.rs +++ b/src/discord/mod.rs @@ -306,6 +306,8 @@ pub async fn run_bot(global_data: GlobalData) { admin::remove_social_credit_phrase(), admin::list_social_credit_phrases(), admin::remove_movie(), + admin::reset_random_score(), + admin::remove_random_result(), album::add_image(), album::list_albums(), birthday::add_birthday(), @@ -329,6 +331,7 @@ pub async fn run_bot(global_data: GlobalData) { joke::list_random(), joke::real_roll(), joke::roll(), + joke::list_randoms(), little_fren::adopt(), little_fren::checkup(), little_fren::feed(), diff --git a/src/error.rs b/src/error.rs index d1ecb0a..7eb827d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -25,6 +25,7 @@ pub enum Error { CTAError(cta_api::Error), NoImageFound, PipelineArgumentError(ModifyImageArgError), + NoRandomFound, } impl StdError for Error {} @@ -114,6 +115,7 @@ impl Display for Error { Error::CTAError(err) => write!(f, "The CTA had an error, I'm shocked: {}", err), Error::NoImageFound => write!(f, "Image not found"), Error::PipelineArgumentError(err) => write!(f, "{}", err), + Error::NoRandomFound => write!(f, "No random found"), } } } diff --git a/src/image_manipulation/mod.rs b/src/image_manipulation/mod.rs index 715326b..8a15220 100644 --- a/src/image_manipulation/mod.rs +++ b/src/image_manipulation/mod.rs @@ -1,6 +1,6 @@ use crate::album_manager::{AlbumManager, ImageQuery, ImageSort}; use crate::error::Error; -use crate::models::motivation::{MotivationConfig}; +use crate::models::motivation::MotivationConfig; use j_db::database::Database; use j_db::error::JDbError; use magick_rust::{CompositeOperator, DrawingWand, FilterType, MagickWand, PixelWand}; @@ -320,8 +320,8 @@ impl FromStr for ModifyImage { Err(Self::Err::NotAllArgumentsSupplied) } else { let alignment_str = args.next().unwrap(); - let alignment: Alignment = Alignment::from_str(alignment_str) - .map_err(Self::Err::InvalidAlignment)?; + let alignment: Alignment = + Alignment::from_str(alignment_str).map_err(Self::Err::InvalidAlignment)?; let background_album = args.next().map(|s| s.to_string()); Ok(Self::GreenScreen { diff --git a/src/migrations/migration_8_fix_metadata_id.rs b/src/migrations/migration_8_fix_metadata_id.rs new file mode 100644 index 0000000..0fdcf32 --- /dev/null +++ b/src/migrations/migration_8_fix_metadata_id.rs @@ -0,0 +1,34 @@ +use j_db::database::{DB_METADATA_ID, Database}; +use j_db::migration::Migration; +use json::JsonValue; + +pub struct Migration8FixMetadataId {} + +impl Migration for Migration8FixMetadataId { + fn up(&self, db: &Database) -> j_db::error::Result<()> { + let tree = db.db.open_tree("DBMetadata")?; + + tree.iter().for_each(|md| { + if let Ok((md_id, _md_bytes)) = md { + let _ = tree.remove(md_id).is_ok(); + } + }); + + let mut md = JsonValue::new_object(); + + md["id"] = JsonValue::Number(json::number::Number::from(DB_METADATA_ID)); + md["version"] = JsonValue::Number(json::number::Number::from(self.version())); + + tree.insert(DB_METADATA_ID.to_be_bytes(), md.to_string().as_bytes())?; + + Ok(()) + } + + fn down(&self, _db: &Database) -> j_db::error::Result<()> { + Ok(()) + } + + fn version(&self) -> u64 { + 8 + } +} diff --git a/src/migrations/mod.rs b/src/migrations/mod.rs index 64a2618..720447d 100644 --- a/src/migrations/mod.rs +++ b/src/migrations/mod.rs @@ -2,6 +2,7 @@ use crate::migrations::migration_4_update_random::Migration4UpdateRandoms; use crate::migrations::migration_5_update_motivation::Migration5UpdateMotivation; use crate::migrations::migration_6_add_social_credit::Migration6AddSocialCredit; use crate::migrations::migration_7_flip_bounds::Migration7FlipBounds; +use crate::migrations::migration_8_fix_metadata_id::Migration8FixMetadataId; use crate::migrations::migration2_remove_imgur::Migration2RemoveImgur; use crate::migrations::migration3_remove_img::Migration3RemoveImage; use j_db::database::Database; @@ -14,8 +15,9 @@ mod migration_4_update_random; mod migration_5_update_motivation; mod migration_6_add_social_credit; mod migration_7_flip_bounds; +mod migration_8_fix_metadata_id; -pub const CURRENT_DB_VERSION: u64 = 7; +pub const CURRENT_DB_VERSION: u64 = 8; #[allow(clippy::single_match)] pub fn do_migration(db: &Database) { @@ -64,6 +66,12 @@ pub fn do_migration(db: &Database) { Direction::Up, ) .unwrap(), + 8 => migration::do_migration::( + db, + Migration8FixMetadataId {}, + Direction::Up, + ) + .unwrap(), _ => {} } } diff --git a/src/models/random.rs b/src/models/random.rs index 7ef520c..f512faa 100644 --- a/src/models/random.rs +++ b/src/models/random.rs @@ -96,18 +96,20 @@ impl RandomConfig { .next()) } - pub fn get_response(&self, db: &Database) -> Result, Error> { - let mut responses: Vec = db + fn get_responses(&self, db: &Database) -> Result, Error> { + Ok(db .filter(|_, random: &Random| self.responses.contains(&random.id().unwrap()))? - .collect(); + .collect()) + } - if !responses.iter().any(|r| r.score > 0) { - for response in &mut responses { - response.score = MAX_SCORE; + pub fn get_response(&self, db: &Database) -> Result, Error> { + let responses = self.get_responses(db)?; - db.insert(response.clone())?; - } - } + let responses = if !responses.iter().any(|r| r.score > 0) { + self.reset_scores_internal(db, responses)? + } else { + responses + }; if responses.is_empty() { return Ok(None); @@ -123,4 +125,27 @@ impl RandomConfig { Ok(Some(resp.response)) } + + pub fn reset_scores(db: &Database, random: &str) -> Result<(), Error> { + let random = Self::get_random(db, random)?.ok_or(Error::NoRandomFound)?; + let responses = random.get_responses(db)?; + + random.reset_scores_internal(db, responses)?; + + Ok(()) + } + + fn reset_scores_internal( + &self, + db: &Database, + mut responses: Vec, + ) -> Result, Error> { + for response in &mut responses { + response.score = MAX_SCORE; + + db.insert(response.clone())?; + } + + Ok(responses) + } }