Skip to content

Commit

Permalink
Merge pull request #34 from stifskere/compiler
Browse files Browse the repository at this point in the history
Compilador
  • Loading branch information
SergioRibera authored Jul 24, 2024
2 parents 10b005e + 31bcdb5 commit f1c94b1
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 3 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/target
Secrets*.toml
.vscode
*.dca
*.dca
.idea
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ regex = "1.10.2"
once_cell = "1.18.0"

gen_welcome = { version = "0.1.0", path = "crates/gen_welcome" }
urlencoding = "2.1.3"

[dependencies.parking_lot]
version = "0.12"
Expand Down
3 changes: 2 additions & 1 deletion src/config/general_command_loader.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::events::join::guild_member_addition;
use crate::general_commands::meetups::MEETUPS_COMMAND;
use crate::general_commands::ping::PING_COMMAND;
use crate::general_commands::compile::COMPILE_COMMAND;
use crate::general_commands::songbird_commands::DEAFEN_COMMAND;
use crate::general_commands::songbird_commands::JOIN_COMMAND;
use crate::general_commands::songbird_commands::LEAVE_COMMAND;
Expand All @@ -13,7 +14,7 @@ use serenity::{async_trait, prelude::Context};
use serenity::{model::prelude::Member, prelude::EventHandler};

#[group]
#[commands(ping, meetups, deafen, join, leave, mute, ting, undeafen, unmute)]
#[commands(ping, meetups, deafen, join, leave, mute, ting, undeafen, unmute, compile)]
pub struct General;

#[async_trait]
Expand Down
258 changes: 258 additions & 0 deletions src/general_commands/compile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
use std::time::Duration;
use regex::Regex;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use serenity::all::{Context, CreateMessage, Message, ReactionType};
use serenity::all::standard::CommandResult;
use serenity::all::standard::macros::command;
use serenity::builder::{CreateEmbed, CreateEmbedFooter};
use urlencoding::encode;
use tokio::time::sleep;
use tracing::error;

static LANGUAGE_ALIASES: &[(&str, &str)] = &[
("objc", "objective-c"),
("kt", "kotlin"),
("cs", "csharp"),
("py", "python"),
("py3", "python3"),
("js", "javascript"),
("coffee", "coffeescript"),
("fs", "fsharp"),
("rs", "rust"),
("lisp", "commonlisp"),
("ts", "typescript"),
("bf", "brainfuck")
];


static LANGUAGES: &[&str] = &["c", "cpp", "objective-c", "java", "kotlin", "scala",
"swift", "csharp", "go", "haskell", "erlang", "perl", "python", "python3",
"ruby", "php", "bash", "r", "javascript", "coffeescript", "vb", "cobol", "fsharp", "d",
"clojure", "elixir", "mysql", "rust", "scheme", "commonlisp", "nadesiko", "typescript",
"brainfuck", "plain"
];

static MISSING_CODE_BLOCK: &str
= "Falta un bloque de código, colocalo con \\`\\`\\` <tu código> \\`\\`\\`.";
static MISSING_LANGUAGE: &str
= "Falta especificar un lenguaje a tu bloque de código, especificalo después de los \\`\\`\\`.";
static INVALID_LANGUAGE: &str
= "El lenguaje especificado es invalido, los lenguajes validos son: ";
static INVALID_RESPONSE: &str
= "La respuesta recibida del compilador no se pudo leer.";

#[derive(Deserialize)]
struct RunnerResponse {
id: String,
status: String,
}

#[derive(Deserialize, Serialize)]
struct RunnerDetails {
build_stderr: Option<String>,
build_exit_code: Option<i32>,
build_result: Option<String>,

stdout: Option<String>,
stderr: Option<String>,
result: Option<String>,
exit_code: Option<i32>
}

async fn compile_code(language: String, code: String, args: String) -> Option<RunnerResponse> {
Client::new()
.post(format!(
"https://api.paiza.io/runners/create?source_code={}&language={}&api_key=guest{}",
encode(&*code),
encode(&*language),
if args.is_empty() { "".to_string() } else { format!("&input={args}") }
))
.send()
.await
.unwrap()
.json::<RunnerResponse>()
.await
.inspect_err(|e| error!("Hubo un error: {e:?}"))
.ok()
}

async fn check_status(runner_id: String) -> Option<RunnerResponse> {
Client::new()
.get(format!(
"https://api.paiza.io/runners/get_status?id={}&api_key=guest",
encode(&*runner_id)
))
.send()
.await
.unwrap()
.json::<RunnerResponse>()
.await
.inspect_err(|e| error!("Hubo un error: {e:?}"))
.ok()
}

async fn check_details(runner_id: String) -> Option<RunnerDetails> {
Client::new()
.get(format!(
"https://api.paiza.io/runners/get_details?id={}&api_key=guest",
encode(&*runner_id)
))
.send()
.await
.unwrap()
.json::<RunnerDetails>()
.await
.inspect_err(|e| error!("Hubo un error: {e:?}"))
.ok()
}

#[command]
pub async fn compile(ctx: &Context, msg: &Message) -> CommandResult {
msg.react(ctx, ReactionType::Unicode("🫡".to_string()))
.await
.unwrap();

let parts: Vec<&str> = Regex::new(r"[ \n]")
.unwrap()
.splitn(&*msg.content, 2)
.collect();

if parts.len() < 2 {
msg.reply(ctx, MISSING_CODE_BLOCK).await?;
return Ok(());
}

let args_and_code = &parts[1..].join(" ");
let start_code = args_and_code
.find("```")
.map(|idx| idx + 3);
let end_code = args_and_code[start_code.unwrap_or(0)..]
.find("```")
.map(|idx| start_code.unwrap_or(0) + idx);

let mut code_block
= if let (Some(start), Some(end)) = (start_code, end_code) {
Some(args_and_code[start..end].to_string())
} else {
msg.reply(ctx, MISSING_CODE_BLOCK).await?;
return Ok(());
}.unwrap();

let mut language = if let Some(start) = start_code {
let lang_end = args_and_code[start..]
.find('\n')
.unwrap_or(0);
&args_and_code[start..start + lang_end]
} else {
""
}
.to_string()
.to_lowercase();

if language.is_empty() {
msg.reply(ctx, MISSING_LANGUAGE).await?;
return Ok(());
}

code_block = code_block[language.len()..].to_string();

language = LANGUAGE_ALIASES.iter()
.find_map(|(key, value)|
if key.to_string() == language { Some(value.to_string()) } else { None
})
.unwrap_or(language);

if language == "rust" {
msg.react(ctx, ReactionType::Unicode("🦀".to_string())).await.unwrap();
}

if !LANGUAGES.contains(&&*language) {
msg.reply(ctx, format!(
"{INVALID_LANGUAGE} {}",
LANGUAGES.join(", ")
)).await?;
return Ok(());
}

let args = args_and_code[end_code.unwrap() + 3..]
.to_string()
.replace("\n", " ");

let api_response = compile_code(language, code_block, args).await;

if let Some(parsed_res) = api_response {
let mut response = parsed_res;
while response.status != "completed" {
sleep(Duration::from_secs(3)).await;
response = if let Some(new_status)
= check_status(response.id).await {
new_status
} else {
msg.reply(ctx, INVALID_RESPONSE).await?;
return Ok(());
};
}

let mut response_embed = CreateEmbed::default();

if let Some(build_details) = check_details(response.id).await {
if build_details.build_result.unwrap_or("success".to_string()) != "success" {
response_embed = response_embed
.title("Error de build!")
.description(format!(
"```\n{}\n```",
build_details.build_stderr.unwrap_or(
"<no se proporciono ningún error de build.>".to_string()
)
))
.color(0xFF0000)
.footer(CreateEmbedFooter::new(format!(
"El compilador salio con el código: {}",
build_details.build_exit_code.unwrap_or_default()
)));
} else if build_details.result.unwrap_or("success".to_string()) != "success" {
response_embed = response_embed
.title("Error de ejecución!")
.description(format!(
"```\n{}\n```",
build_details.stderr.unwrap_or(
"<no se proporciono ningún error de ejecución>".to_string()
)
))
.color(0xFF0000)
.footer(CreateEmbedFooter::new(format!(
"El programa salio con el código: {}",
build_details.exit_code.unwrap_or_default()
)))
} else {
response_embed = response_embed
.title("El código se ejecuto correctamente")
.description(format!(
"```\n{}\n```",
build_details.stdout.unwrap_or(
"<el código no escribió en la consola.>".to_string()
)
))
.color(0x00FF00)
.footer(CreateEmbedFooter::new(format!(
"El programa salio con el código: {}",
build_details.exit_code.unwrap_or_default()
)))
}

msg.channel_id
.send_message(
ctx,
CreateMessage::new().embed(response_embed).reference_message(msg)
)
.await?;
} else {
msg.reply(ctx, INVALID_RESPONSE).await?;
}
} else {
msg.reply(ctx, INVALID_RESPONSE).await?;
}

Ok(())
}
1 change: 1 addition & 0 deletions src/general_commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod meetups;
pub mod ping;
pub mod songbird_commands;
pub mod compile;
2 changes: 1 addition & 1 deletion src/slash_commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ pub mod id;
pub mod invite;
pub mod crate_lib;
pub mod ping;
pub mod sugerencia;
pub mod sugerencia;

0 comments on commit f1c94b1

Please sign in to comment.