extern crate lastfm; extern crate dotenvy; use serenity::async_trait; use serenity::model::channel::Message; use serenity::prelude::*; use serenity::builder::{CreateAttachment, CreateMessage}; extern crate magick_rust; use magick_rust::{magick_wand_genesis, MagickWand, PixelWand, CompositeOperator, ColorspaceType}; use std::env; use std::sync::Once; static START: Once = Once::new(); struct Handler; enum Reply { Image(Vec), Text(String) } macro_rules! handle_magick_result { ($a: expr, $b: literal) => { match $a { Ok(_) => {}, Err(e) => return Reply::Text(format!("Error: {} {}", $b, e)), } } } macro_rules! handle_magick_option { ($a: expr, $b: literal) => { match $a { Some(res) => res, None => return Reply::Text($b.to_string()), } } } async fn fmi(_arg: &str) -> Reply { let client = lastfm::Client::::from_env("seoxi"); let now_playing = match client.now_playing().await { Ok(np) => np, Err(e) => return Reply::Text(format!("Error: grabbing last.fm user data failed {e}")), }; if let Some(track) = now_playing { let image_uri = match track.image.extralarge { Some(iu) => iu, None => return Reply::Text("Error: getting image uri failed".to_string()), }; let mut base_color = PixelWand::new(); #[allow(unused_mut)] let mut main_wand = MagickWand::new(); #[allow(unused_mut)] let mut art_wand = MagickWand::new(); //#[allow(unused_mut)] let mut art_wand_cluster = MagickWand::new(); handle_magick_result!(base_color.set_color("#7f7f7f"), "Failed to set base color"); handle_magick_result!(art_wand.read_image(image_uri.as_str()), "Failed to read image from uri"); handle_magick_result!(art_wand.transform_image_colorspace(ColorspaceType::Lab), "Failed to set art colorspace"); let (art_width, art_height) = (art_wand.get_image_width(), art_wand.get_image_height()); handle_magick_result!(art_wand_cluster.new_image(art_width, art_height, &base_color), "Failed to init cluster image"); handle_magick_result!(art_wand_cluster.compose_images(&art_wand, CompositeOperator::SrcOver, true, 0, 0), "Failed to copy onto cluster image"); // least stupid way I could figure out to do this, since it seems that MagickWand::new_from_wand() doesn't work handle_magick_result!(art_wand_cluster.set_image_colorspace(ColorspaceType::Lab), "Failed to set cluster colorspace"); handle_magick_result!(art_wand_cluster.kmeans(5, 200, 0.001), "Failed to run kmeans"); let mut colors = handle_magick_option!(art_wand_cluster.get_image_histogram(), "Failed to get color histogram"); println!("{}", colors.len()); colors.sort_by_cached_key(|color| -(color.get_color_count() as isize)); handle_magick_result!(main_wand.new_image(548, 147, &colors[0]), "Failed to create wand"); handle_magick_result!(main_wand.transform_image_colorspace(ColorspaceType::Lab), "Failed to set main colorspace"); handle_magick_result!(art_wand.adaptive_resize_image(124, 124), "Failed to resize art"); handle_magick_result!(art_wand_cluster.adaptive_resize_image(124, 124), "Failed to resize art_cluster"); handle_magick_result!(main_wand.compose_images(&art_wand, CompositeOperator::SrcOver, false, 12, 12), "Failed to combine images"); handle_magick_result!(main_wand.compose_images(&art_wand_cluster, CompositeOperator::SrcOver, false, 160, 12), "Failed to combine images"); Reply::Image(main_wand.write_image_blob("png").unwrap()) } else { Reply::Text("Nothing playing.".to_string()) } } async fn set(arg: &str) -> Reply { Reply::Text(format!("set user {}", arg)) } #[async_trait] impl EventHandler for Handler { async fn message(&self, ctx: Context, msg: Message) { let mut cmd_iter = msg.content.split(" "); let cmd = cmd_iter.next().unwrap_or(""); let arg = cmd_iter.next().unwrap_or(""); let resp = match cmd { ".fmi" => Some(fmi(arg).await), ".set" => Some(set(arg).await), _ => None }; if let Some(reply) = resp { let m = match reply { Reply::Image(img) => { let file = CreateAttachment::bytes(img, "fmi.png"); CreateMessage::new().add_file(file) }, Reply::Text(txt) => CreateMessage::new().content(txt) }; let message = msg.channel_id.send_message(&ctx.http, m).await; if let Err(why) = message { println!("Error sending message: {why:?}"); } } } } #[tokio::main] async fn main() { START.call_once(|| { dotenvy::dotenv().expect("Failed to load .env"); magick_wand_genesis(); }); // Login with a bot token from the environment let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment"); // Set gateway intents, which decides what events the bot will be notified about let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::DIRECT_MESSAGES | GatewayIntents::MESSAGE_CONTENT; // Create a new instance of the Client, logging in as a bot. let mut client = Client::builder(&token, intents).event_handler(Handler).await.expect("Err creating client"); // Start listening for events by starting a single shard if let Err(why) = client.start().await { println!("Client error: {why:?}"); } }