Newer
Older
use serenity::model::channel::{AttachmentType, Message, ReactionType};
use serenity::model::id::UserId;
use serenity::prelude::*;
use std::collections::HashMap;
use std::str::FromStr;
use xbasic::basic_io::BasicIO;
use xbasic::expr::ExprValue;
use xbasic::xbasic::XBasicBuilder;
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");
}
}
macro_rules! get_user_programs {
($self: expr, $user_id: expr) => {
$self.programs.lock().await.get_mut($user_id).unwrap()
programs: Arc<Mutex<HashMap<UserId, Program>>>,
conn: Arc<Mutex<PgConnection>>,
}
pub fn new(conn: Arc<Mutex<PgConnection>>) -> Self {
Self {
programs: Arc::new(Mutex::new(HashMap::new())),
conn,
}
}
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().await.contains_key(&msg.author.id) {
}
"RUN" => {
self.interpreter_run(msg, ctx).await;
}
"LIST" => {
self.interpreter_list(msg, ctx).await;
}
"DIR" => {
if self.list_saved_programs(msg, ctx).await.is_none() {
msg.channel_id
.say(&ctx, "Could not get list of programs from database.")
.await
.unwrap();
}
}
"PUBDIR" => {
if self.list_published_programs(msg, ctx).await.is_none() {
msg.channel_id
.say(
&ctx,
"Could not get list of published programs from database.",
)
.await
.unwrap();
_ => {
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;
}
if let Some(name) = line.strip_prefix("PUB ") {
if self.publish_program(name, msg, ctx).await.is_none() {
msg.channel_id
.await
.unwrap();
}
}
if let Some(name) = line.strip_prefix("UNPUB ") {
if self.unpublish_program(name, msg, ctx).await.is_none() {
msg.channel_id
.await
.unwrap();
}
}
if let Some(id_str) = line.strip_prefix("PUBLOAD ") {
match id_str.parse::<i32>() {
Ok(id) => {
if self.load_published_program(msg, ctx, id).await.is_none() {
msg.channel_id
.await
.unwrap();
}
}
Err(_) => {
msg.channel_id
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 _ =
let _ = get_user_programs!(self, &msg.author.id).code.remove(&num);
async fn interpreter_stop(&self, msg: &Message) {
self.programs.lock().await.remove(&msg.author.id).unwrap();
async fn list_saved_programs(&self, msg: &Message, ctx: &Context) -> Option<()> {
let program_names =
Program::list_programs_by_user(&*self.conn.lock().await, msg.author.id)?;
msg.channel_id
.say(
&ctx,
format!(
"You have {} programs saved:\r\n```\r\n{}\r\n```",
program_names.len(),
program_names.join("\r\n")
),
)
.await
.ok()?;
Some(())
}
async fn list_published_programs(&self, msg: &Message, ctx: &Context) -> Option<()> {
let program_names: Vec<String> =
.iter()
.map(|row| format!("{}\t{}", row.0, row.1))
.collect();
msg.channel_id
.say(
&ctx,
format!(
"Found {} public programs:\r\n```\r\nid\tname\r\n{}\r\n```\r\n Load one with `PUBLOAD <id>`",
program_names.len(),
program_names.join("\r\n")),
)
.await
.unwrap();
Some(())
}
async fn publish_program(&self, name: &str, msg: &Message, ctx: &Context) -> Option<()> {
Program::set_program_published(&*self.conn.lock().await, name, msg.author.id, true)?;
.await
.unwrap();
Some(())
}
async fn unpublish_program(&self, name: &str, msg: &Message, ctx: &Context) -> Option<()> {
Program::set_program_published(&*self.conn.lock().await, name, msg.author.id, false)?;
.await
.unwrap();
Some(())
}
async fn load_published_program(&self, msg: &Message, ctx: &Context, id: i32) -> Option<()> {
async fn interpreter_list(&self, msg: &Message, ctx: &Context) {
msg.channel_id
.say(
&ctx,
format!(
"```\n{}\n```",
self.programs.lock().await[&msg.author.id].stringy_line_nums()
async fn interpreter_load(&self, name: &str, msg: &Message, ctx: &Context) {
let result = get_user_programs!(self, &msg.author.id).load_program(
msg.author.id,
name,
);
match result {
Some(_) => {
msg.channel_id
.await
.unwrap();
}
None => {
msg.channel_id
.say(&ctx, "Could not load program into memory.")
.await
.unwrap();
}
}
}
async fn interpreter_save(&self, name: &str, msg: &Message, ctx: &Context) {
let result = get_user_programs!(self, &msg.author.id).save_program(
msg.author.id,
name,
);
match result {
Some(_) => {
msg.channel_id
.await
.unwrap();
}
None => {
msg.channel_id
.say(&ctx, "Could not save program.")
.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,
};
let code = self.programs.lock().await[&msg.author.id].stringify();
let (output, fb, errors) = task::spawn_blocking(move || {
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;
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);
ExprValue::Decimal(0.0)
})
.unwrap();
let mut xb = xbb.build();
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;
}
}
}
#[async_trait]