use crate::framebuffer::FrameBuffer; use crate::program::Program; use diesel::PgConnection; use phf::phf_map; use serenity::async_trait; use serenity::http::AttachmentType; use serenity::model::channel::{Message, ReactionType}; use serenity::model::id::UserId; use serenity::model::prelude::Ready; use serenity::prelude::*; use std::borrow::Cow; use std::collections::HashMap; use std::str::FromStr; use std::sync::{Arc, Mutex}; use xbasic::basic_io::BasicIO; use xbasic::expr::ExprValue; use xbasic::xbasic::XBasicBuilder; static EMOJI_MAP: phf::Map<&'static str, &'static str> = phf_map! { "cat" => "🐈", "chicken" => "🐔", "spaghetti" => "🍝", "dog" => "🐕", "bot" => "🤖", "mango" => "🥭", "banana" => "🍌", "bee" => "🐝" }; struct DiscordIO { s: String, frame: Option<FrameBuffer>, } impl DiscordIO { fn new() -> Self { Self { s: String::new(), frame: None, } } } impl BasicIO for DiscordIO { fn read_line(&mut self) -> String { unimplemented!() } fn write_line(&mut self, line: String) { self.s += &*(line + "\r\n"); } } pub(crate) struct Handler { programs: Arc<Mutex<HashMap<UserId, Program>>>, conn: Arc<Mutex<PgConnection>>, } impl Handler { pub fn new(conn: PgConnection) -> Self { Self { programs: Arc::new(Mutex::new(HashMap::new())), conn: Arc::new(Mutex::new(conn)), } } } #[async_trait] impl EventHandler for Handler { async fn message(&self, ctx: Context, msg: Message) { for (key, value) in EMOJI_MAP.entries() { let msg_lower = format!(" {} ", msg.content.to_lowercase()); if msg_lower.contains(&format!(" {} ", key)) || msg_lower.contains(&format!(" {}s ", key)) { let reaction_type = match ReactionType::from_str(value) { Ok(x) => x, Err(x) => { println!("Could not react: {}", x); return; } }; if let Err(e) = msg.react(&ctx, reaction_type).await { println!("Error reacting: {}", e); } } } for line in msg.content.split('\n') { if self.programs.lock().unwrap().contains_key(&msg.author.id) { match line { "!STOP" => { self.programs .lock() .unwrap() .remove(&msg.author.id) .unwrap(); } "RUN" => { let thinking_react = match ReactionType::from_str("🤔") { Ok(react) => msg.react(&ctx, react).await.ok(), Err(_) => None, }; let code = self.programs.lock().unwrap()[&msg.author.id].stringify(); let io = DiscordIO::new(); let (output, fb, errors) = { let mut xbb = XBasicBuilder::new(io); xbb.compute_limit(1000000000); xbb.define_function("setframe".to_owned(), 2, |args, io| { let w = args[0].clone().into_decimal() as u32; let h = args[1].clone().into_decimal() as u32; io.frame = Some(FrameBuffer::new(w, h)); ExprValue::Decimal(0.0) }) .unwrap(); xbb.define_function("setpixel".to_owned(), 5, |args, io| { let x = args[0].clone().into_decimal() as u32; let y = args[1].clone().into_decimal() as u32; let red = args[2].clone().into_decimal() as u8; let green = args[3].clone().into_decimal() as u8; let blue = args[4].clone().into_decimal() as u8; match &mut io.frame { Some(fb) => { fb.set_pixel(x, y, red, green, blue, 255); } None => {} } ExprValue::Decimal(0.0) }) .unwrap(); let mut xb = xbb.build(); let _ = xb.run(&format!("{}\n", code)); let errors = if xb.error_handler.had_errors || xb.error_handler.had_runtime_error { Some(xb.error_handler.errors.join("\n")) } else { None }; (xb.get_io().s.clone(), xb.get_io().frame.clone(), errors) }; if !output.is_empty() { msg.channel_id.say(&ctx, output).await.unwrap(); } if let Some(fb) = &fb { let buf = fb.as_png_vec(); msg.channel_id .send_message(&ctx, |e| { e.add_file(AttachmentType::Bytes { data: Cow::Borrowed(&buf), filename: "output.png".to_string(), }); e }) .await .unwrap(); } if let Some(e) = errors { msg.channel_id.say(&ctx, e).await.unwrap(); if let Ok(react) = ReactionType::from_str("❌") { let _ = msg.react(&ctx, react).await; } } else if let Ok(react) = ReactionType::from_str("✅") { let _ = msg.react(&ctx, react).await; } if let Some(t) = thinking_react { let _ = t.delete(&ctx).await; } } "LIST" => { msg.channel_id .say( &ctx, format!( "```\n{}\n```", self.programs.lock().unwrap()[&msg.author.id] .stringy_line_nums() ), ) .await .unwrap(); } _ => { if let Some(name) = line.strip_prefix("SAVE ") { let result = self .programs .lock() .unwrap() .get_mut(&msg.author.id) .unwrap() .save_program(&self.conn.lock().unwrap(), msg.author.id, name); match result { Some(_) => { msg.channel_id .say(&ctx, format!("Saved as {}", name)) .await .unwrap(); } None => { msg.channel_id .say(&ctx, "Could not save program.") .await .unwrap(); } } return; } if let Some(name) = line.strip_prefix("LOAD ") { let result = self .programs .lock() .unwrap() .get_mut(&msg.author.id) .unwrap() .load_program(&self.conn.lock().unwrap(), msg.author.id, name); match result { Some(_) => { msg.channel_id .say(&ctx, format!("Loaded {} into memory.", name)) .await .unwrap(); } None => { msg.channel_id .say(&ctx, "Could not load program into memory.") .await .unwrap(); } } return; } let mut split = line.splitn(2, ' '); let first = split.next().unwrap(); if let Ok(num) = first.parse::<u32>() { match split.next() { Some(x) => { if x.is_empty() { let _ = self .programs .lock() .unwrap() .get_mut(&msg.author.id) .unwrap() .code .remove(&num); return; } self.programs .lock() .unwrap() .get_mut(&msg.author.id) .unwrap() .code .insert(num, x.to_owned()); } None => { let _ = self .programs .lock() .unwrap() .get_mut(&msg.author.id) .unwrap() .code .remove(&num); } } } } } } if line == "!START" { self.programs .lock() .unwrap() .insert(msg.author.id, Program::new()); } } } async fn ready(&self, _: Context, ready: Ready) { println!("{} is connected", ready.user.name); } }