diff --git a/Cargo.lock b/Cargo.lock index 6914cd2..75e4661 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1093,7 +1093,7 @@ dependencies = [ [[package]] name = "fren" -version = "1.6.3" +version = "1.7.0" dependencies = [ "axum 0.8.1", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index 8c5c117..f80be1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fren" -version = "1.6.3" +version = "1.7.0" edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/discord/admin.rs b/src/discord/admin.rs index 672aa8c..bb409e9 100644 --- a/src/discord/admin.rs +++ b/src/discord/admin.rs @@ -7,6 +7,7 @@ use crate::models::lil_fren::{ draw_resonance_cascade, draw_sick, draw_sleep, draw_standing, draw_tax_fraud, }; use crate::models::managed_roles::ManagedRole; +use crate::models::movie::Movie; use crate::models::social_credit::SocialCreditPhrase; use crate::models::task::Task; use crate::user::User; @@ -18,7 +19,6 @@ use poise::serenity_prelude::{ FormattedTimestampStyle, MessageBuilder, Timestamp, UserId, }; use std::borrow::Cow; -use crate::models::movie::Movie; pub async fn is_admin(ctx: Context<'_>) -> Result { Ok(ctx.data().cfg.admins.contains(&ctx.author().id)) @@ -341,9 +341,13 @@ pub async fn list_social_credit_phrases(ctx: Context<'_>) -> Result<(), Error> { /// Remove a movie from the library #[poise::command(prefix_command, category = "Admin", check = "is_admin")] -pub async fn remove_movie(ctx: Context<'_>, #[description = "Name of the movie"] movie: String,) -> Result<(), Error> { +pub async fn remove_movie( + ctx: Context<'_>, + #[description = "Name of the movie"] movie: String, +) -> Result<(), Error> { Movie::remove_movie(&ctx.data().db, &movie)?; - - ctx.reply(format!("Removed *{}* from the library", movie)).await?; + + ctx.reply(format!("Removed *{}* from the library", movie)) + .await?; Ok(()) } diff --git a/src/discord/motivate.rs b/src/discord/image.rs similarity index 75% rename from src/discord/motivate.rs rename to src/discord/image.rs index fdc8b64..6eff971 100644 --- a/src/discord/motivate.rs +++ b/src/discord/image.rs @@ -108,6 +108,44 @@ pub async fn create_greenscreen_image( 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(); @@ -159,7 +197,7 @@ pub async fn create_motivation_image(motivation: Motivation) -> Result, } /// Become motivated -#[poise::command(prefix_command, category = "Motivation")] +#[poise::command(prefix_command, category = "Image")] pub async fn motivation( ctx: Context<'_>, #[description = "Album to use for the motivation"] album_name: Option, @@ -190,7 +228,7 @@ pub async fn motivation( } /// Greenscreen your friends! -#[poise::command(prefix_command, category = "Motivation")] +#[poise::command(prefix_command, category = "Image")] pub async fn green_screen( ctx: Context<'_>, #[description = "Album to use as the foreground image"] foreground: Option, @@ -245,8 +283,60 @@ pub async fn green_screen( Ok(()) } +/// Border +#[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?; + + ctx.channel_id() + .send_message( + &ctx, + CreateMessage::new() + .content("I made dis:") + .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 = "Motivation")] +#[poise::command(prefix_command, category = "Image")] pub async fn motivation_add_album( ctx: Context<'_>, #[description = "Album to add to the motivation pool"] album: String, diff --git a/src/discord/mod.rs b/src/discord/mod.rs index 0843b8d..56749b1 100644 --- a/src/discord/mod.rs +++ b/src/discord/mod.rs @@ -5,9 +5,9 @@ mod celeryman; mod color; mod emoji_race; mod fren_coin; +mod image; mod joke; mod little_fren; -mod motivate; mod movie; mod role; pub(crate) mod shop; @@ -335,9 +335,10 @@ pub async fn run_bot(global_data: GlobalData) { little_fren::give_medicine(), little_fren::give_water(), little_fren::play(), - motivate::motivation(), - motivate::motivation_add_album(), - motivate::green_screen(), + image::motivation(), + image::motivation_add_album(), + image::green_screen(), + image::overlay(), movie::add_movie(), movie::list_movies(), movie::rate_movie(), diff --git a/src/discord/movie.rs b/src/discord/movie.rs index 60a5659..366616f 100644 --- a/src/discord/movie.rs +++ b/src/discord/movie.rs @@ -80,7 +80,9 @@ pub struct MovieResp { #[poise::command(prefix_command, category = "Movies")] pub async fn movie( ctx: Context<'_>, - #[description = "Name of the movie"] #[rest] movie: String, + #[description = "Name of the movie"] + #[rest] + movie: String, ) -> Result<(), Error> { let movie = Movie::get_movie(&ctx.data().db, &movie)?; @@ -88,7 +90,10 @@ pub async fn movie( let metadata: MovieResp = client .get(OMDB_URL) - .query(&[("t", movie.name.clone()), ("apikey", ctx.data().cfg.omdb_key.clone())]) + .query(&[ + ("t", movie.name.clone()), + ("apikey", ctx.data().cfg.omdb_key.clone()), + ]) .send() .await? .json() @@ -97,7 +102,12 @@ pub async fn movie( let mut ratings = MessageBuilder::new(); for (user, rating) in &movie.ratings { - ratings.push_line(format!("* {}: {} {}", user.mention(), rating, &movie.rating_object)); + ratings.push_line(format!( + "* {}: {} {}", + user.mention(), + rating, + &movie.rating_object + )); } ctx.send( @@ -106,9 +116,13 @@ pub async fn movie( .title(format!("{} ({})", movie.name, metadata.year)) .description(metadata.plot) .image(movie.poster.clone()) - .field("Flock Rating", format!("{} {}", movie.calculate_rating(), movie.rating_object), true) + .field( + "Flock Rating", + format!("{} {}", movie.calculate_rating(), movie.rating_object), + true, + ) .field("Ratings:", ratings.build(), false) - .color(Color::from_rgb(0x11, 0x40, 0xaa)) + .color(Color::from_rgb(0x11, 0x40, 0xaa)), ), ) .await?; @@ -126,12 +140,15 @@ pub async fn list_movies(ctx: Context<'_>) -> Result<(), Error> { msg_builder.push_line("# Fleecebuster Movies:"); for movie in &movies { - msg_builder.push_line(format!("* *{}* {} {}", movie.name, movie.calculate_rating(), movie.rating_object)); + msg_builder.push_line(format!( + "* *{}* {} {}", + movie.name, + movie.calculate_rating(), + movie.rating_object + )); } ctx.reply(msg_builder.build()).await?; Ok(()) } - - diff --git a/src/discord/shop.rs b/src/discord/shop.rs index 22b7305..53ef55b 100644 --- a/src/discord/shop.rs +++ b/src/discord/shop.rs @@ -1,5 +1,5 @@ use crate::config::GlobalData; -use crate::discord::motivate::create_motivation_image; +use crate::discord::image::create_motivation_image; use crate::discord::{Context, get_role}; use crate::error::Error; use crate::inventory::{InventoryError, ItemData, ItemType, Operation, nft_value}; diff --git a/src/models/movie.rs b/src/models/movie.rs index 0b172f1..8efcc76 100644 --- a/src/models/movie.rs +++ b/src/models/movie.rs @@ -58,11 +58,9 @@ impl Movie { .next() .ok_or(JDbError::NotFound)?) } - + pub fn get_movies(db: &Database) -> Result, Error> { - Ok(db - .filter(|_, _movie: &Movie| true)? - .collect()) + Ok(db.filter(|_, _movie: &Movie| true)?.collect()) } pub fn remove_movie(db: &Database, name: &str) -> Result { @@ -86,8 +84,7 @@ impl Movie { pub fn calculate_rating(&self) -> f32 { if self.ratings.is_empty() { 0.0 - } - else { + } else { self.ratings.values().copied().sum::() / self.ratings.len() as f32 } }