Another emoji reaction bot(this one is for Discord)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

291 lines
6.5 KiB

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