|
|
@ -51,6 +51,12 @@ impl BasicIO for DiscordIO { |
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! get_user_programs {
|
|
|
|
($self: expr, $user_id: expr) => {
|
|
|
|
$self.programs.lock().unwrap().get_mut($user_id).unwrap()
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) struct Handler {
|
|
|
|
programs: Arc<Mutex<HashMap<UserId, Program>>>,
|
|
|
|
conn: Arc<Mutex<PgConnection>>,
|
|
|
@ -63,240 +69,245 @@ impl Handler { |
|
|
|
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;
|
|
|
|
async fn interpret_line(&self, msg: &Message, ctx: &Context, line: &str) {
|
|
|
|
// TODO we lock the mutex to check, but unlock before locking again later
|
|
|
|
// allows another thread to screw it up
|
|
|
|
// we should lock once for this entire function
|
|
|
|
if self.programs.lock().unwrap().contains_key(&msg.author.id) {
|
|
|
|
match line {
|
|
|
|
"!STOP" => {
|
|
|
|
self.interpreter_stop(msg);
|
|
|
|
}
|
|
|
|
"RUN" => {
|
|
|
|
self.interpreter_run(msg, ctx).await;
|
|
|
|
}
|
|
|
|
"LIST" => {
|
|
|
|
self.interpreter_list(msg, ctx).await;
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
if let Some(name) = line.strip_prefix("SAVE ") {
|
|
|
|
self.interpreter_save(name, msg, ctx).await;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(name) = line.strip_prefix("LOAD ") {
|
|
|
|
self.interpreter_load(name, msg, ctx).await;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 _ =
|
|
|
|
get_user_programs!(&self, &msg.author.id).code.remove(&num);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
get_user_programs!(&self, &msg.author.id)
|
|
|
|
.code
|
|
|
|
.insert(num, x.to_owned());
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
let _ = get_user_programs!(&self, &msg.author.id).code.remove(&num);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
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 => {}
|
|
|
|
}
|
|
|
|
if line == "!START" {
|
|
|
|
self.interpreter_start(msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ExprValue::Decimal(0.0)
|
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
fn interpreter_stop(&self, msg: &Message) {
|
|
|
|
self.programs
|
|
|
|
.lock()
|
|
|
|
.unwrap()
|
|
|
|
.remove(&msg.author.id)
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut xb = xbb.build();
|
|
|
|
fn interpreter_start(&self, msg: &Message) {
|
|
|
|
self.programs
|
|
|
|
.lock()
|
|
|
|
.unwrap()
|
|
|
|
.insert(msg.author.id, Program::new());
|
|
|
|
}
|
|
|
|
|
|
|
|
let _ = xb.run(&format!("{}\n", code));
|
|
|
|
async fn interpreter_list(&self, msg: &Message, ctx: &Context) {
|
|
|
|
msg.channel_id
|
|
|
|
.say(
|
|
|
|
&ctx,
|
|
|
|
format!(
|
|
|
|
"```\n{}\n```",
|
|
|
|
self.programs.lock().unwrap()[&msg.author.id].stringy_line_nums()
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
let errors = if xb.error_handler.had_errors
|
|
|
|
|| xb.error_handler.had_runtime_error
|
|
|
|
{
|
|
|
|
Some(xb.error_handler.errors.join("\n"))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
async fn interpreter_load(&self, name: &str, msg: &Message, ctx: &Context) {
|
|
|
|
let result = get_user_programs!(&self, &msg.author.id).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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(xb.get_io().s.clone(), xb.get_io().frame.clone(), errors)
|
|
|
|
};
|
|
|
|
async fn interpreter_save(&self, name: &str, msg: &Message, ctx: &Context) {
|
|
|
|
let result = get_user_programs!(&self, &msg.author.id).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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !output.is_empty() {
|
|
|
|
msg.channel_id.say(&ctx, output).await.unwrap();
|
|
|
|
}
|
|
|
|
async fn interpreter_run(&self, msg: &Message, ctx: &Context) {
|
|
|
|
let thinking_react = match ReactionType::from_str("🤔") {
|
|
|
|
Ok(react) => msg.react(&ctx, react).await.ok(),
|
|
|
|
Err(_) => None,
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(fb) = &fb {
|
|
|
|
let buf = fb.as_png_vec();
|
|
|
|
let code = self.programs.lock().unwrap()[&msg.author.id].stringify();
|
|
|
|
let io = DiscordIO::new();
|
|
|
|
|
|
|
|
msg.channel_id
|
|
|
|
.send_message(&ctx, |e| {
|
|
|
|
e.add_file(AttachmentType::Bytes {
|
|
|
|
data: Cow::Borrowed(&buf),
|
|
|
|
filename: "output.png".to_string(),
|
|
|
|
});
|
|
|
|
let (output, fb, errors) = {
|
|
|
|
let mut xbb = XBasicBuilder::new(io);
|
|
|
|
xbb.compute_limit(1000000000);
|
|
|
|
|
|
|
|
e
|
|
|
|
})
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
io.frame = Some(FrameBuffer::new(w, h));
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
ExprValue::Decimal(0.0)
|
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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 line == "!START" {
|
|
|
|
self.programs
|
|
|
|
.lock()
|
|
|
|
.unwrap()
|
|
|
|
.insert(msg.author.id, Program::new());
|
|
|
|
if let Some(t) = thinking_react {
|
|
|
|
let _ = t.delete(&ctx).await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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') {
|
|
|
|
self.interpret_line(&msg, &ctx, line).await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn ready(&self, _: Context, ready: Ready) {
|
|
|
|