Skip to content
Snippets Groups Projects
main.rs 7.01 KiB
Newer Older
Stephen D's avatar
Stephen D committed
mod config;

use phf::phf_map;
use serenity::async_trait;
Stephen D's avatar
Stephen D committed
use serenity::http::AttachmentType;
Stephen D's avatar
Stephen D committed
use serenity::model::channel::{Message, ReactionType};
Stephen D's avatar
Stephen D committed
use serenity::model::id::UserId;
Stephen D's avatar
Stephen D committed
use serenity::model::prelude::Ready;
use serenity::prelude::*;
use serenity::Client;
Stephen D's avatar
Stephen D committed
use std::borrow::Cow;
use std::collections::HashMap;
Stephen D's avatar
Stephen D committed
use std::str::FromStr;
Stephen D's avatar
Stephen D committed
use std::sync::Arc;
use xbasic::basic_io::BasicIO;
use xbasic::expr::ExprValue;
use xbasic::xbasic::XBasicBuilder;
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
#[derive(Clone)]
struct FrameBuffer {
	width: u32,
	height: u32,
	buffer: Vec<u8>,
}

impl FrameBuffer {
	fn new(width: u32, height: u32) -> Self {
		Self {
			width,
			height,
			buffer: vec![0; width as usize * height as usize * 4],
		}
	}

	fn set_pixel(&mut self, x: u32, y: u32, red: u8, green: u8, blue: u8, alpha: u8) {
		if x >= self.width || y >= self.height {
			return;
		}

		let x = x as usize;
		let y = y as usize;
		self.buffer[(x + self.width as usize * y) * 4] = red;
		self.buffer[(x + self.width as usize * y) * 4 + 1] = green;
		self.buffer[(x + self.width as usize * y) * 4 + 2] = blue;
		self.buffer[(x + self.width as usize * y) * 4 + 3] = alpha;
	}

	fn as_png_vec(&self) -> Vec<u8> {
		let mut buffer: Vec<u8> = Vec::new();

		{
			let mut encoder = png::Encoder::new(&mut buffer, self.width, self.height);
			encoder.set_color(png::ColorType::RGBA);
			encoder.set_depth(png::BitDepth::Eight);
			let mut writer = encoder.write_header().unwrap();
			writer.write_image_data(&self.buffer).unwrap();
		}

		buffer
	}
}

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");
	}
}

struct Program {
	code: HashMap<u32, String>,
}

impl Program {
	fn new() -> Self {
		Self {
			code: HashMap::new(),
		}
	}

	fn stringify(&self) -> String {
		let mut code: Vec<(u32, String)> =
			self.code.iter().map(|(a, b)| (*a, b.to_owned())).collect();
		code.sort_by(|a, b| a.0.cmp(&b.0));

		code.into_iter()
			.map(|a| a.1)
			.collect::<Vec<String>>()
			.join("\n")
	}

	fn stringy_line_nums(&self) -> String {
		let mut code: Vec<(u32, String)> =
			self.code.iter().map(|(a, b)| (*a, b.to_owned())).collect();
		code.sort_by(|a, b| a.0.cmp(&b.0));

		code.into_iter()
			.map(|a| format!("{}\t{}", a.0, a.1))
			.collect::<Vec<String>>()
			.join("\n")
	}
}

struct Handler {
	programs: Arc<Mutex<HashMap<UserId, Program>>>,
}
Stephen D's avatar
Stephen D committed

static EMOJI_MAP: phf::Map<&'static str, &'static str> = phf_map! {
"cat" => "🐈",
"chicken" => "🐔",
"spaghetti" => "🍝",
"dog" => "🐕",
"bot" => "🤖",
"mango" => "🥭",
"banana" => "🍌",
"bee" => "🐝"
};

#[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);
				}
			}
		}
Stephen D's avatar
Stephen D committed

		for line in msg.content.split('\n') {
			if self.programs.lock().await.contains_key(&msg.author.id) {
				match line {
					"!STOP" => {
						self.programs.lock().await.remove(&msg.author.id).unwrap();
					}
					"RUN" => {
						let code = self.programs.lock().await[&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)
						};

						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();
						}
					}
					"LIST" => {
						msg.channel_id
							.say(
								&ctx,
								format!(
									"```\n{}\n```",
									self.programs.lock().await[&msg.author.id].stringy_line_nums()
								),
							)
							.await
							.unwrap();
					}
					_ => {
						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()
											.await
											.get_mut(&msg.author.id)
											.unwrap()
											.code
											.remove(&num);
										return;
									}

									self.programs
										.lock()
										.await
										.get_mut(&msg.author.id)
										.unwrap()
										.code
										.insert(num, x.to_owned());
								}
								None => {
									let _ = self
										.programs
										.lock()
										.await
										.get_mut(&msg.author.id)
										.unwrap()
										.code
										.remove(&num);
								}
							}
						}
					}
				}
			}

			if line == "!START" {
				self.programs
					.lock()
					.await
					.insert(msg.author.id, Program::new());
			}
		}
Stephen D's avatar
Stephen D committed
	}

	async fn ready(&self, _: Context, ready: Ready) {
		println!("{} is connected", ready.user.name);
	}
}

#[tokio::main]
async fn main() {
	let config = config::get_conf();
	let token = config.token;

	let mut client = Client::builder(&token)
Stephen D's avatar
Stephen D committed
		.event_handler(Handler {
			programs: Arc::new(Mutex::new(HashMap::new())),
		})
Stephen D's avatar
Stephen D committed
		.await
		.expect("Error creating client");
	if let Err(e) = client.start().await {
		println!("Client error: {}", e);
	}
}