diff --git a/Cargo.lock b/Cargo.lock index ac27b55..2e8cf1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,6 +96,28 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "bindgen" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -144,6 +166,15 @@ version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -185,6 +216,17 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "clang-sys" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "2.34.0" @@ -373,6 +415,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + [[package]] name = "encoding_rs" version = "0.8.31" @@ -436,6 +484,7 @@ name = "fren" version = "0.1.0" dependencies = [ "config", + "magick_rust", "ndm", "rand", "regex", @@ -534,6 +583,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "globset" version = "0.4.9" @@ -803,6 +858,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "levenshtein" version = "1.0.5" @@ -815,6 +876,16 @@ version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "link-cplusplus" version = "1.0.7" @@ -849,6 +920,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "magick_rust" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31ad8b956bdc8d2bcb4e6feaeac3642a1d91892271c9e70f2a5c408a755c26e3" +dependencies = [ + "bindgen", + "libc", + "pkg-config", +] + [[package]] name = "memchr" version = "2.5.0" @@ -1076,6 +1158,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "percent-encoding" version = "2.2.0" @@ -1376,6 +1464,12 @@ dependencies = [ "ordered-multimap", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustls" version = "0.20.7" @@ -1577,6 +1671,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + [[package]] name = "siphasher" version = "0.3.10" @@ -2223,6 +2323,17 @@ dependencies = [ "webpki", ] +[[package]] +name = "which" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 5c57a86..32c1710 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ rand = "0.8.5" tera = "1.17.1" ndm = "0.9.9" regex = "1.7.0" +magick_rust = "0.17.0" [dependencies.serenity] version = "0.11.5" diff --git a/src/config.rs b/src/config.rs index 920e563..0844aa6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -35,6 +35,13 @@ pub struct RandomConfig { pub responses: Vec, } +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct MotivationConfig { + pub album: Vec, + pub action: Vec, + pub goal: Vec, +} + #[derive(Debug, Deserialize, Serialize, Clone)] pub struct BotConfig { pub bot_token: String, @@ -49,6 +56,8 @@ pub struct BotConfig { pub wallet_manager: WalletManager, + pub motivation: MotivationConfig, + pub insults: Vec, pub compliments: Vec, diff --git a/src/discord/mod.rs b/src/discord/mod.rs index a89a6d6..e5127d0 100644 --- a/src/discord/mod.rs +++ b/src/discord/mod.rs @@ -5,6 +5,7 @@ pub mod color; pub mod emoji_race; pub mod fren_coin; pub mod joke; +pub(crate) mod motivate; pub mod story; use crate::discord::fren_coin::give_coin; diff --git a/src/discord/motivate.rs b/src/discord/motivate.rs new file mode 100644 index 0000000..1247e82 --- /dev/null +++ b/src/discord/motivate.rs @@ -0,0 +1,103 @@ +use crate::imgur::{get_album_images, Image}; +use crate::{command, group, GlobalData}; +use magick_rust::{DrawingWand, MagickWand, PixelWand}; +use rand::prelude::IteratorRandom; +use rand::thread_rng; +use serenity::client::Context; +use serenity::framework::standard::{Args, CommandResult}; +use serenity::model::channel::{AttachmentType, Message}; +use std::borrow::Cow; + +#[group] +#[commands(motivation)] +pub struct Motivate; + +#[command] +#[only_in(guilds)] +#[description("Let's give you motivation")] +async fn motivation(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { + let data = ctx.data.read().await; + let global_data = data.get::().unwrap(); + + let album = &global_data + .cfg + .motivation + .album + .iter() + .choose(&mut thread_rng()) + .unwrap(); + + let images = get_album_images(&global_data.cfg.imgur_client_id, album).await?; + let motivation_image_link: &Image = images.iter().choose(&mut thread_rng()).unwrap(); + + let motivation_image_blob = reqwest::get(&motivation_image_link.link) + .await? + .bytes() + .await?; + + let action = global_data + .cfg + .motivation + .action + .iter() + .choose(&mut thread_rng()) + .unwrap(); + let goal = global_data + .cfg + .motivation + .goal + .iter() + .choose(&mut thread_rng()) + .unwrap(); + let motivation = format!("{} {}", action, goal); + + let image = { + let mut wand = MagickWand::new(); + let mut border_wand = PixelWand::new(); + wand.read_image_blob(motivation_image_blob)?; + + border_wand.set_color("white")?; + let width = wand.get_image_width(); + let border = width / 100; + wand.border_image( + &border_wand, + border, + border, + magick_rust::bindings::CompositeOperator_OverCompositeOp, + )?; + + 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, + )?; + + 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::bindings::AlignType_CenterAlign); + wand.annotate_image(&text_wand, text_pos_x, text_pos_y, 0.0, &motivation)?; + wand.write_image_blob("png")? + }; + + msg.channel_id + .send_message(&ctx.http, |m| { + m.content("Today's motivation") + .add_file(AttachmentType::Bytes { + data: Cow::from(image), + filename: "motivate.png".to_string(), + }) + }) + .await?; + + Ok(()) +} diff --git a/src/imgur/mod.rs b/src/imgur/mod.rs index ce0dfd2..6f98f71 100644 --- a/src/imgur/mod.rs +++ b/src/imgur/mod.rs @@ -25,6 +25,8 @@ impl Display for ImgurError { } } +impl std::error::Error for ImgurError {} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct AlbumData { images: Option>, diff --git a/src/main.rs b/src/main.rs index 72465ea..92d98b7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,13 +8,17 @@ mod wallet; use crate::config::{Args, BotConfig, Channel, GlobalData}; use crate::discord::emoji_race::RaceMessage; use crate::discord::unrecognised_command_hook; +use magick_rust::magick_wand_genesis; use serenity::framework::standard::macros::{command, group, help, hook}; use serenity::framework::standard::StandardFramework; use serenity::prelude::*; +use std::sync::Once; use structopt::StructOpt; const BAD_APPLE: &str = include_str!("assets/bad_apple.txt"); +static START: Once = Once::new(); + #[tokio::main] async fn main() { let args: Args = Args::from_args(); @@ -27,6 +31,10 @@ async fn main() { } }; + START.call_once(|| { + magick_wand_genesis(); + }); + let global_data = match GlobalData::new(args, cfg).await { Ok(global_data) => global_data, Err(err) => { @@ -45,6 +53,7 @@ async fn main() { .group(&discord::story::STORY_GROUP) .group(&discord::fren_coin::FRENCOIN_GROUP) .group(&discord::emoji_race::EMOJIRACE_GROUP) + .group(&discord::motivate::MOTIVATE_GROUP) .unrecognised_command(unrecognised_command_hook) .bucket("bad_apple", |b| b.delay(60 * 10)) .await