|
|
@ -1,304 +1,24 @@ |
|
|
|
mod config;
|
|
|
|
#[macro_use]
|
|
|
|
extern crate diesel;
|
|
|
|
|
|
|
|
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::*;
|
|
|
|
mod config;
|
|
|
|
mod framebuffer;
|
|
|
|
mod handler;
|
|
|
|
mod models;
|
|
|
|
mod program;
|
|
|
|
mod schema;
|
|
|
|
|
|
|
|
use crate::handler::Handler;
|
|
|
|
use diesel::{Connection, PgConnection};
|
|
|
|
use dotenv::dotenv;
|
|
|
|
use serenity::Client;
|
|
|
|
use std::borrow::Cow;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::str::FromStr;
|
|
|
|
use std::sync::Arc;
|
|
|
|
use xbasic::basic_io::BasicIO;
|
|
|
|
use xbasic::expr::ExprValue;
|
|
|
|
use xbasic::xbasic::XBasicBuilder;
|
|
|
|
|
|
|
|
#[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>>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
use std::env;
|
|
|
|
|
|
|
|
match &mut io.frame {
|
|
|
|
Some(fb) => {
|
|
|
|
fb.set_pixel(x, y, red, green, blue, 255);
|
|
|
|
}
|
|
|
|
None => {}
|
|
|
|
}
|
|
|
|
fn establish_connection() -> PgConnection {
|
|
|
|
dotenv().ok();
|
|
|
|
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn ready(&self, _: Context, ready: Ready) {
|
|
|
|
println!("{} is connected", ready.user.name);
|
|
|
|
}
|
|
|
|
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
|
|
|
PgConnection::establish(&database_url).expect("Error connecting to database")
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::main]
|
|
|
@ -306,10 +26,10 @@ async fn main() { |
|
|
|
let config = config::get_conf();
|
|
|
|
let token = config.token;
|
|
|
|
|
|
|
|
let conn = establish_connection();
|
|
|
|
|
|
|
|
let mut client = Client::builder(&token)
|
|
|
|
.event_handler(Handler {
|
|
|
|
programs: Arc::new(Mutex::new(HashMap::new())),
|
|
|
|
})
|
|
|
|
.event_handler(Handler::new(conn))
|
|
|
|
.await
|
|
|
|
.expect("Error creating client");
|
|
|
|
if let Err(e) = client.start().await {
|
|
|
|