From dfffe202df43fa5c036a39516651e7a549eb1c6d Mon Sep 17 00:00:00 2001 From: Joey Hines Date: Sun, 6 Jul 2025 20:38:09 -0600 Subject: [PATCH] Added edit_img command + Refactored image editing so it's all handled in the same way + Added pipeline concept for more advanced edits --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/discord/image.rs | 316 +++++---------------------- src/discord/mod.rs | 1 + src/discord/shop.rs | 13 +- src/error.rs | 11 + src/image_manipulation/mod.rs | 387 ++++++++++++++++++++++++++++++++++ src/main.rs | 1 + src/models/motivation.rs | 37 ++-- 9 files changed, 477 insertions(+), 293 deletions(-) create mode 100644 src/image_manipulation/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 75e4661..4469bdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1093,7 +1093,7 @@ dependencies = [ [[package]] name = "fren" -version = "1.7.0" +version = "1.8.0" dependencies = [ "axum 0.8.1", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index f80be1c..0133ca7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fren" -version = "1.7.0" +version = "1.8.0" edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/discord/image.rs b/src/discord/image.rs index 6eff971..79d2d7c 100644 --- a/src/discord/image.rs +++ b/src/discord/image.rs @@ -1,200 +1,14 @@ -use crate::album_manager::{AlbumQuery, ImageQuery, ImageSort}; +use crate::album_manager::AlbumQuery; use crate::discord::Context; use crate::error::Error; -use crate::models::motivation::{Motivation, MotivationConfig}; -use magick_rust::{CompositeOperator, DrawingWand, FilterType, MagickWand, PixelWand}; +use crate::image_manipulation::{ + BaseImage, ModifyImage, ModifyImageArgError, create_green_screen_image, + create_motivation_image, create_overlay_image, image_pipeline, +}; +use crate::models::motivation::MotivationConfig; use poise::serenity_prelude::builder::{CreateAttachment, CreateMessage}; -use rand::Rng; -use rand::distr::{Distribution, StandardUniform}; -use reqwest::{Client, Url}; -use serde::Deserialize; use std::borrow::Cow; - -#[derive(Debug, Deserialize)] -pub enum Alignment { - Left, - Center, - Right, -} - -impl Alignment { - pub fn offset(&self) -> f64 { - match self { - Alignment::Left => 0.25, - Alignment::Center => 0.5, - Alignment::Right => 0.75, - } - } -} - -impl Distribution for StandardUniform { - fn sample(&self, rng: &mut R) -> Alignment { - match rng.random_range(0..=2) { - 0 => Alignment::Left, - 1 => Alignment::Center, - _ => Alignment::Right, - } - } -} - -pub async fn create_greenscreen_image( - background_image_url: Url, - foreground_image_url: Url, - alignment: Alignment, -) -> Result, Error> { - let client = Client::new(); - - let background_image_blob = client - .get(background_image_url) - .send() - .await? - .bytes() - .await? - .to_vec(); - - let foreground_image_blob = client - .get(foreground_image_url) - .send() - .await? - .bytes() - .await? - .to_vec(); - - let background_wand = MagickWand::new(); - let foreground_wand = MagickWand::new(); - - background_wand.read_image_blob(background_image_blob)?; - foreground_wand.read_image_blob(foreground_image_blob)?; - - let background_height = background_wand.get_image_height(); - let background_width = background_wand.get_image_width(); - - let foreground_height = foreground_wand.get_image_height(); - let foreground_width = foreground_wand.get_image_width(); - - let ratio = foreground_height as f64 / foreground_width as f64; - - let (scale_height, scale_width) = - if foreground_height > background_height && foreground_width > background_width { - ( - background_height as f64 / foreground_height as f64, - background_width as f64 / foreground_width as f64, - ) - } else if foreground_height > background_height { - let scale = background_height as f64 / foreground_height as f64; - - (scale, scale * ratio) - } else if foreground_width > background_width { - let scale = background_width as f64 / foreground_width as f64; - - (scale * ratio, scale) - } else { - (1.0, 1.0) - }; - - foreground_wand.scale_image(scale_width * 0.6, scale_height * 0.6, FilterType::Lanczos2)?; - - let pos_x = background_width as f64 * alignment.offset() - - foreground_wand.get_image_width() as f64 / 2.0; - let pos_y = background_height - foreground_wand.get_image_height(); - background_wand.compose_images( - &foreground_wand, - CompositeOperator::Atop, - true, - pos_x as isize, - pos_y as isize, - )?; - - Ok(background_wand.write_image_blob("png")?) -} - -pub async fn create_overlay_image( - background_image_url: Url, - overlay_image_url: Url, -) -> Result, Error> { - let client = Client::new(); - - let background_image_blob = client - .get(background_image_url) - .send() - .await? - .bytes() - .await? - .to_vec(); - - let overlay_image_blob = client - .get(overlay_image_url) - .send() - .await? - .bytes() - .await? - .to_vec(); - - let background_wand = MagickWand::new(); - let foreground_wand = MagickWand::new(); - - background_wand.read_image_blob(background_image_blob)?; - foreground_wand.read_image_blob(overlay_image_blob)?; - - let overlay_height = background_wand.get_image_height(); - let overlay_width = background_wand.get_image_width(); - - foreground_wand.resize_image(overlay_width, overlay_height, FilterType::Lanczos2)?; - - background_wand.compose_images(&foreground_wand, CompositeOperator::Atop, true, 0, 0)?; - - Ok(background_wand.write_image_blob("png")?) -} - -pub async fn create_motivation_image(motivation: Motivation) -> Result, Error> { - let client = Client::new(); - - let motivation_image_blob = client - .get(motivation.image.link) - .send() - .await? - .bytes() - .await? - .to_vec(); - - let text = format!("{} {}", motivation.action, motivation.goal); - - let mut wand = MagickWand::new(); - let mut border_wand = PixelWand::new(); - wand.read_image_blob(motivation_image_blob)?; - - border_wand.set_color(&motivation.border_color)?; - let width = wand.get_image_width(); - let border = width / 100; - wand.border_image( - &border_wand, - border, - border, - magick_rust::bindings::CompositeOperator_OverCompositeOp.into(), - )?; - - border_wand.set_color("black")?; - let width = wand.get_image_width(); - let border = width * 20 / 100; - wand.border_image( - &border_wand, - border, - border, - magick_rust::bindings::CompositeOperator_OverCompositeOp.into(), - )?; - - let text_pos_x = wand.get_image_width() as f64 / 2.0; - let text_pos_y = wand.get_image_height() as f64 - (wand.get_image_height() as f64 * 0.05); - - let mut text_wand = DrawingWand::new(); - let mut text_color_wand = PixelWand::new(); - text_color_wand.set_color("white")?; - text_wand.set_fill_color(&text_color_wand); - text_wand.set_font_size(0.07 * (wand.get_image_width() as f64)); - text_wand.set_text_alignment(magick_rust::AlignType::Center); - wand.annotate_image(&text_wand, text_pos_x, text_pos_y, 0.0, &text)?; - Ok(wand.write_image_blob("jpg")?) -} +use std::str::FromStr; /// Become motivated #[poise::command(prefix_command, category = "Image")] @@ -202,15 +16,8 @@ pub async fn motivation( ctx: Context<'_>, #[description = "Album to use for the motivation"] album_name: Option, ) -> Result<(), Error> { - let motivation = MotivationConfig::generate_motivation( - &ctx.data().db, - &ctx.data().picox, - "white", - album_name, - ) - .await?; - - let image = create_motivation_image(motivation).await?; + let image = + create_motivation_image(&ctx.data().db, &ctx.data().picox, "white", album_name).await?; ctx.channel_id() .send_message( @@ -234,38 +41,8 @@ pub async fn green_screen( #[description = "Album to use as the foreground image"] foreground: Option, #[description = "Album to use for the background image"] background: Option, ) -> Result<(), Error> { - let foreground = ctx - .data() - .picox - .query_image(ImageQuery { - album: foreground, - tags: vec![], - order: ImageSort::Random, - limit: 1, - }) - .await? - .first() - .cloned() - .unwrap(); - - let background = ctx - .data() - .picox - .query_image(ImageQuery { - album: background, - tags: vec![], - order: ImageSort::Random, - limit: 1, - }) - .await? - .first() - .cloned() - .unwrap(); - - let alignment: Alignment = rand::random(); - let image = - create_greenscreen_image(background.link.clone(), foreground.link.clone(), alignment) + create_green_screen_image(&ctx.data().db, &ctx.data().picox, foreground, background) .await?; ctx.channel_id() @@ -283,42 +60,15 @@ pub async fn green_screen( Ok(()) } -/// Border +/// Overlay an image on another #[poise::command(prefix_command, category = "Image")] pub async fn overlay( ctx: Context<'_>, #[description = "Album to use as the border image"] overlay: Option, #[description = "Album to use for the background image"] background: Option, ) -> Result<(), Error> { - let foreground = ctx - .data() - .picox - .query_image(ImageQuery { - album: overlay, - tags: vec![], - order: ImageSort::Random, - limit: 1, - }) - .await? - .first() - .cloned() - .unwrap(); - - let background = ctx - .data() - .picox - .query_image(ImageQuery { - album: background, - tags: vec![], - order: ImageSort::Random, - limit: 1, - }) - .await? - .first() - .cloned() - .unwrap(); - - let image = create_overlay_image(background.link.clone(), foreground.link.clone()).await?; + let image = + create_overlay_image(&ctx.data().db, &ctx.data().picox, overlay, background).await?; ctx.channel_id() .send_message( @@ -335,6 +85,46 @@ pub async fn overlay( Ok(()) } +/// Mix and match editing tricks to create art for the soul +#[poise::command(prefix_command, category = "Image")] +pub async fn edit_img( + ctx: Context<'_>, + #[description = "Base image album to use for your master piece, use 'any' if you want a random image"] + base_image_album: String, + #[description = "One or many edits to make"] pipeline: Vec, +) -> Result<(), Error> { + let base_image = if base_image_album.eq_ignore_ascii_case("any") { + BaseImage::ByAlbum { album_name: None } + } else { + BaseImage::ByAlbum { + album_name: Some(base_image_album), + } + }; + + let pipeline: Result, ModifyImageArgError> = pipeline + .iter() + .map(|s| ModifyImage::from_str(s.as_str())) + .collect(); + + let pipeline = pipeline?; + + let image = image_pipeline(&ctx.data().db, &ctx.data().picox, base_image, pipeline).await?; + + ctx.channel_id() + .send_message( + &ctx, + CreateMessage::new() + .content("I fear this is, the most perfect art created by humans. its all over, we have peacked:") + .add_file(CreateAttachment::bytes( + Cow::from(image), + "overlay.jpg".to_string(), + )), + ) + .await?; + + Ok(()) +} + /// Add an album to the pool of images to be used for motivations #[poise::command(prefix_command, category = "Image")] pub async fn motivation_add_album( diff --git a/src/discord/mod.rs b/src/discord/mod.rs index 56749b1..2f82f60 100644 --- a/src/discord/mod.rs +++ b/src/discord/mod.rs @@ -339,6 +339,7 @@ pub async fn run_bot(global_data: GlobalData) { image::motivation_add_album(), image::green_screen(), image::overlay(), + image::edit_img(), movie::add_movie(), movie::list_movies(), movie::rate_movie(), diff --git a/src/discord/shop.rs b/src/discord/shop.rs index 53ef55b..3c405e8 100644 --- a/src/discord/shop.rs +++ b/src/discord/shop.rs @@ -1,9 +1,8 @@ use crate::config::GlobalData; -use crate::discord::image::create_motivation_image; use crate::discord::{Context, get_role}; use crate::error::Error; +use crate::image_manipulation::create_motivation_image; use crate::inventory::{InventoryError, ItemData, ItemType, Operation, nft_value}; -use crate::models::motivation::MotivationConfig; use crate::models::task::{Task, TaskType}; use crate::user::{User, UserError}; use poise::serenity_prelude::all::{ @@ -432,14 +431,8 @@ pub async fn restock_shop( tokio::fs::create_dir(&global_data.cfg.nft_path).await? } - let nft_motivation = MotivationConfig::generate_motivation( - &global_data.db, - &global_data.picox, - "gold", - None, - ) - .await?; - let nft = create_motivation_image(nft_motivation).await?; + let nft = + create_motivation_image(&global_data.db, &global_data.picox, "gold", None).await?; let mut hasher = DefaultHasher::new(); hasher.write(&nft); let nft_hash = hasher.finish(); diff --git a/src/error.rs b/src/error.rs index 4412213..d1ecb0a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,5 @@ use crate::discord::voices::VoiceError; +use crate::image_manipulation::ModifyImageArgError; use crate::user; use magick_rust::MagickError; use serde::ser::StdError; @@ -22,6 +23,8 @@ pub enum Error { IoError(std::io::Error), VoiceError(VoiceError), CTAError(cta_api::Error), + NoImageFound, + PipelineArgumentError(ModifyImageArgError), } impl StdError for Error {} @@ -86,6 +89,12 @@ impl From for Error { } } +impl From for Error { + fn from(value: ModifyImageArgError) -> Self { + Self::PipelineArgumentError(value) + } +} + impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { @@ -103,6 +112,8 @@ impl Display for Error { Error::IoError(err) => write!(f, "IO Error: {}", err), Error::VoiceError(err) => write!(f, "Voice error: {}", err), 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), } } } diff --git a/src/image_manipulation/mod.rs b/src/image_manipulation/mod.rs new file mode 100644 index 0000000..715326b --- /dev/null +++ b/src/image_manipulation/mod.rs @@ -0,0 +1,387 @@ +use crate::album_manager::{AlbumManager, ImageQuery, ImageSort}; +use crate::error::Error; +use crate::models::motivation::{MotivationConfig}; +use j_db::database::Database; +use j_db::error::JDbError; +use magick_rust::{CompositeOperator, DrawingWand, FilterType, MagickWand, PixelWand}; +use rand::Rng; +use rand::distr::{Distribution, StandardUniform}; +use reqwest::{Client, Url}; +use serde::Deserialize; +use std::str::FromStr; +use thiserror::Error; + +pub async fn create_motivation_image( + db: &Database, + picox: &AlbumManager, + color: &str, + album_name: Option, +) -> Result, Error> { + image_pipeline( + db, + picox, + BaseImage::Motivation { album_name }, + vec![ModifyImage::Motivation { + border_color: Some(color.to_string()), + }], + ) + .await +} + +pub async fn create_green_screen_image( + db: &Database, + picox: &AlbumManager, + foreground_album_name: Option, + background_album_name: Option, +) -> Result, Error> { + image_pipeline( + db, + picox, + BaseImage::ByAlbum { + album_name: foreground_album_name, + }, + vec![ModifyImage::GreenScreen { + align: rand::random(), + background_album: background_album_name, + }], + ) + .await +} + +pub async fn create_overlay_image( + db: &Database, + picox: &AlbumManager, + overlay_album_name: Option, + background_album_name: Option, +) -> Result, Error> { + image_pipeline( + db, + picox, + BaseImage::ByAlbum { + album_name: background_album_name, + }, + vec![ModifyImage::Overlay { + overlay_album: overlay_album_name, + }], + ) + .await +} + +pub async fn get_image_from_url(url: Url) -> Result, Error> { + let client = Client::new(); + let image_blob = client.get(url).send().await?.bytes().await?.to_vec(); + + Ok(image_blob) +} + +pub async fn get_album_image( + picox: &AlbumManager, + album: Option, +) -> Result, Error> { + let img = picox + .query_image(ImageQuery { + album, + tags: vec![], + order: ImageSort::Random, + limit: 1, + }) + .await? + .first() + .cloned() + .ok_or(Error::NoImageFound)?; + + get_image_from_url(img.link).await +} + +async fn apply_greenscreen( + picox: &AlbumManager, + img_blob: Vec, + background_album: Option, + alignment: &Alignment, +) -> Result, Error> { + let background_image_blob = get_album_image(picox, background_album).await?; + + let background_wand = MagickWand::new(); + let foreground_wand = MagickWand::new(); + + background_wand.read_image_blob(background_image_blob)?; + foreground_wand.read_image_blob(img_blob)?; + + let background_height = background_wand.get_image_height(); + let background_width = background_wand.get_image_width(); + + let foreground_height = foreground_wand.get_image_height(); + let foreground_width = foreground_wand.get_image_width(); + + let ratio = foreground_height as f64 / foreground_width as f64; + + let (scale_height, scale_width) = + if foreground_height > background_height && foreground_width > background_width { + ( + background_height as f64 / foreground_height as f64, + background_width as f64 / foreground_width as f64, + ) + } else if foreground_height > background_height { + let scale = background_height as f64 / foreground_height as f64; + + (scale, scale * ratio) + } else if foreground_width > background_width { + let scale = background_width as f64 / foreground_width as f64; + + (scale * ratio, scale) + } else { + (1.0, 1.0) + }; + + foreground_wand.scale_image(scale_width * 0.6, scale_height * 0.6, FilterType::Lanczos2)?; + + let pos_x = background_width as f64 * alignment.offset() + - foreground_wand.get_image_width() as f64 / 2.0; + let pos_y = background_height - foreground_wand.get_image_height(); + background_wand.compose_images( + &foreground_wand, + CompositeOperator::Atop, + true, + pos_x as isize, + pos_y as isize, + )?; + + Ok(background_wand.write_image_blob("png")?) +} + +async fn apply_overlay( + picox: &AlbumManager, + img_blob: Vec, + overlay_album: Option, +) -> Result, Error> { + let overlay_image_blob = get_album_image(picox, overlay_album).await?; + + let background_wand = MagickWand::new(); + let foreground_wand = MagickWand::new(); + + background_wand.read_image_blob(img_blob)?; + foreground_wand.read_image_blob(overlay_image_blob)?; + + let overlay_height = background_wand.get_image_height(); + let overlay_width = background_wand.get_image_width(); + + foreground_wand.resize_image(overlay_width, overlay_height, FilterType::Lanczos2)?; + + background_wand.compose_images(&foreground_wand, CompositeOperator::Atop, true, 0, 0)?; + + Ok(background_wand.write_image_blob("png")?) +} + +async fn apply_motivation_layer( + db: &Database, + img_blob: Vec, + border_color: Option, +) -> Result, Error> { + let motivation = + MotivationConfig::generate_motivation(db, &border_color.unwrap_or("white".to_string())) + .await?; + + let text = format!("{} {}", motivation.action, motivation.goal); + + let mut wand = MagickWand::new(); + let mut border_wand = PixelWand::new(); + wand.read_image_blob(img_blob)?; + + border_wand.set_color(&motivation.border_color)?; + let width = wand.get_image_width(); + let border = width / 100; + wand.border_image( + &border_wand, + border, + border, + magick_rust::bindings::CompositeOperator_OverCompositeOp.into(), + )?; + + border_wand.set_color("black")?; + let width = wand.get_image_width(); + let border = width * 20 / 100; + wand.border_image( + &border_wand, + border, + border, + magick_rust::bindings::CompositeOperator_OverCompositeOp.into(), + )?; + + let text_pos_x = wand.get_image_width() as f64 / 2.0; + let text_pos_y = wand.get_image_height() as f64 - (wand.get_image_height() as f64 * 0.05); + + let mut text_wand = DrawingWand::new(); + let mut text_color_wand = PixelWand::new(); + text_color_wand.set_color("white")?; + text_wand.set_fill_color(&text_color_wand); + text_wand.set_font_size(0.07 * (wand.get_image_width() as f64)); + text_wand.set_text_alignment(magick_rust::AlignType::Center); + wand.annotate_image(&text_wand, text_pos_x, text_pos_y, 0.0, &text)?; + Ok(wand.write_image_blob("jpg")?) +} + +pub enum ModifyImage { + GreenScreen { + align: Alignment, + background_album: Option, + }, + Overlay { + overlay_album: Option, + }, + Motivation { + border_color: Option, + }, +} + +impl ModifyImage { + pub async fn process_image_blob( + &self, + db: &Database, + picox: &AlbumManager, + img_blob: Vec, + ) -> Result, Error> { + match self { + ModifyImage::GreenScreen { + align, + background_album, + } => apply_greenscreen(picox, img_blob, background_album.clone(), align).await, + ModifyImage::Overlay { overlay_album } => { + apply_overlay(picox, img_blob, overlay_album.clone()).await + } + ModifyImage::Motivation { border_color } => { + apply_motivation_layer(db, img_blob, border_color.clone()).await + } + } + } +} + +#[derive(Debug)] +pub enum BaseImage { + Motivation { album_name: Option }, + ByAlbum { album_name: Option }, +} + +pub async fn image_pipeline( + db: &Database, + picox: &AlbumManager, + base_image: BaseImage, + img_pipeline: Vec, +) -> Result, Error> { + let base_image = match base_image { + BaseImage::Motivation { album_name } => { + MotivationConfig::get_motivation_image(db, picox, album_name).await? + } + BaseImage::ByAlbum { album_name } => picox + .query_image(ImageQuery { + album: album_name, + tags: vec![], + order: ImageSort::Random, + limit: 1, + }) + .await? + .first() + .ok_or(Error::from(JDbError::NotFound))? + .clone(), + }; + + let mut img_blob = get_image_from_url(base_image.link).await?; + + for step in &img_pipeline { + img_blob = step.process_image_blob(db, picox, img_blob).await?; + } + + Ok(img_blob) +} + +#[derive(Error, Debug)] +pub enum ModifyImageArgError { + #[error("No type found matching")] + NoTypeFound, + #[error("Unknown modification type '{0}'")] + UnknownType(String), + #[error("Not all arguments provided")] + NotAllArgumentsSupplied, + #[error("{0}")] + InvalidAlignment(String), +} + +impl FromStr for ModifyImage { + type Err = ModifyImageArgError; + + /// type:arg1,arg2 + fn from_str(s: &str) -> Result { + let mut s_split = s.split(":"); + let mod_type = s_split.next().ok_or(Self::Err::NoTypeFound)?.to_lowercase(); + let mut args = s_split.next().unwrap_or("").split(","); + + match mod_type.as_str() { + "greenscreen" => { + if args.clone().count() < 1 { + 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 background_album = args.next().map(|s| s.to_string()); + + Ok(Self::GreenScreen { + align: alignment, + background_album, + }) + } + } + "overlay" => { + let overlay_album = args.next().map(|s| s.to_string()); + + Ok(Self::Overlay { overlay_album }) + } + "motivation" => { + let border_color = args.next().map(|s| s.to_string()); + + Ok(Self::Motivation { border_color }) + } + _ => Err(Self::Err::UnknownType(mod_type.to_string())), + } + } +} + +#[derive(Debug, Deserialize)] +pub enum Alignment { + Left, + Center, + Right, +} + +impl Alignment { + pub fn offset(&self) -> f64 { + match self { + Alignment::Left => 0.25, + Alignment::Center => 0.5, + Alignment::Right => 0.75, + } + } +} + +impl Distribution for StandardUniform { + fn sample(&self, rng: &mut R) -> Alignment { + match rng.random_range(0..=2) { + 0 => Alignment::Left, + 1 => Alignment::Center, + _ => Alignment::Right, + } + } +} + +impl FromStr for Alignment { + type Err = String; + + fn from_str(s: &str) -> Result { + let s = s.to_lowercase(); + match s.as_str() { + "left" => Ok(Alignment::Left), + "right" => Ok(Alignment::Right), + "center" => Ok(Alignment::Center), + _ => Err("Unknown alignment".to_string()), + } + } +} diff --git a/src/main.rs b/src/main.rs index 24f3529..b6061ba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod album_manager; mod config; mod discord; mod error; +mod image_manipulation; mod inventory; mod migrations; mod models; diff --git a/src/models/motivation.rs b/src/models/motivation.rs index 30011a2..4d7ca26 100644 --- a/src/models/motivation.rs +++ b/src/models/motivation.rs @@ -58,13 +58,28 @@ impl MotivationConfig { pub async fn generate_motivation( db: &Database, - picox: &AlbumManager, border_color: &str, - album_name: Option, ) -> Result { - let motivation = Self::get_motivation_config(db)?; + let actions = RandomConfig::get_random(db, ACTION_RANDOM_GROUP)?.unwrap_or_default(); + let goals = RandomConfig::get_random(db, GOAL_RANDOM_GROUP)?.unwrap_or_default(); + let action = actions.get_response(db)?.unwrap_or_default(); + let goal = goals.get_response(db)?.unwrap_or_default(); + + Ok(Motivation { + action: action.to_string(), + goal: goal.to_string(), + border_color: border_color.to_string(), + }) + } + + pub async fn get_motivation_image( + db: &Database, + picox: &AlbumManager, + album_name: Option, + ) -> Result { let mut images = Vec::new(); + let motivation = Self::get_motivation_config(db)?; if let Some(album_name) = album_name { let query = AlbumQuery { @@ -92,26 +107,12 @@ impl MotivationConfig { let image = images.choose(&mut rng()).ok_or(Error::NoAlbumFound)?; - let actions = RandomConfig::get_random(db, ACTION_RANDOM_GROUP)?.unwrap_or_default(); - let goals = RandomConfig::get_random(db, GOAL_RANDOM_GROUP)?.unwrap_or_default(); - - let action = actions.get_response(db)?.unwrap_or_default(); - let goal = goals.get_response(db)?.unwrap_or_default(); - - let image = picox.get_image_by_id(*image).await?; - - Ok(Motivation { - image, - action: action.to_string(), - goal: goal.to_string(), - border_color: border_color.to_string(), - }) + picox.get_image_by_id(*image).await } } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct Motivation { - pub image: Image, pub action: String, pub goal: String, pub border_color: String,