html terminal
Diffstat (limited to 'src/bot/exec.rs')
| -rw-r--r-- | src/bot/exec.rs | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/src/bot/exec.rs b/src/bot/exec.rs new file mode 100644 index 0000000..8528a8c --- /dev/null +++ b/src/bot/exec.rs @@ -0,0 +1,161 @@ +use super::{Context, Result}; +use emoji::named::*; +use poise::serenity_prelude::*; +use std::process::ExitStatus; +use tokio::sync::broadcast::{channel, error::TryRecvError as ChannelE}; +use tokio::sync::oneshot::{channel as oneshot, error::TryRecvError as OneE}; + +pub async fn sysadmin(c: Context<'_>) -> Result<bool> { + let c = c.author_member().await.ok_or(anyhow::anyhow!("dang"))?; + Ok(sys_ck(&c)) +} + +pub fn sys_ck(c: &Member) -> bool { + c.user.name == "bendn" || c.roles.contains(&RoleId::new(1113997024220696606)) +} + +#[poise::command( + slash_command, + check = "sysadmin", + category = "System Administration", + default_member_permissions = "ADMINISTRATOR", + guild_only +)] +/// Executes any command. Please no `rm -rf /*`. +/// Executes in a shell, so you can >|& as you desire. +pub async fn exec( + c: Context<'_>, + #[description = "command to run in bash"] command: String, +) -> Result<()> { + #[derive(Debug, Clone)] + enum Payload { + Stdout(String), + Stderr(String), + Exit(ExitStatus), + } + let (otx, mut orx) = channel::<Payload>(16); + let (ttx, mut trx) = oneshot::<()>(); + let cc = command.clone(); + tokio::task::spawn(async move { + let mut c = tokio::process::Command::new("bash") + .arg("-c") + .arg(cc) + .env("FORCE_COLOR", "1") + .current_dir(env!("HOME")) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let mut stdout = c.stdout.take().unwrap(); + let mut o = Box::new([0; 1 << 20]); + let mut stderr = c.stderr.take().unwrap(); + loop { + match trx.try_recv() { + Err(e) => match e { + OneE::Closed => _ = c.kill().await, + OneE::Empty => {} + }, + Ok(()) => c.kill().await.unwrap(), + } + tokio::select! { + n = tokio::io::AsyncReadExt::read(&mut stdout, &mut *o) => { + let n = n.unwrap(); + if n != 0 { + let string = String::from_utf8_lossy(&o[..n]).into_owned(); + otx.send(Payload::Stdout(string)).unwrap(); + } + }, + () = tokio::time::sleep(tokio::time::Duration::from_millis(50)) => (), + }; + tokio::select! { + n = tokio::io::AsyncReadExt::read(&mut stderr, &mut *o) => { + let n = n.unwrap(); + if n != 0 { + let string = String::from_utf8_lossy(&o[..n]).into_owned(); + otx.send(Payload::Stderr(string)).unwrap(); + } + }, + () = tokio::time::sleep(tokio::time::Duration::from_millis(50)) => (), + }; + if let Ok(Some(n)) = c.try_wait() { + otx.send(Payload::Exit(n)).unwrap(); + break; + } + tokio::time::sleep(tokio::time::Duration::from_millis(25)).await; + } + }); + let h = poise::send_reply( + c, + poise::CreateReply::default() + .content("<a:lod:1198459385790337164>\u{200b}") + .components(vec![CreateActionRow::Buttons(vec![CreateButton::new( + format!("{}_ctrl_c", c.id()), + ) + .label("C^c") + .style(ButtonStyle::Danger) + .emoji(CANCEL.parse::<ReactionType>().unwrap())])]), + ) + .await?; + let mut dat = String::new(); + let mut ttx = Some(ttx); + let cc = c.serenity_context().clone(); + let cid = c.id(); + let k = tokio::spawn(async move { + while let Some(x) = tokio::select! { + x =async { ComponentInteractionCollector::new(&cc) + .filter(move |press| press.data.custom_id.starts_with(&cid.to_string())) + .timeout(std::time::Duration::from_secs(60 * 60)).await }=> x, + () = tokio::time::sleep(tokio::time::Duration::from_millis(100)) => None, + } { + if sys_ck(x.member.as_ref().unwrap()) { + _ = ttx.take().unwrap().send(()); + } else { + x.create_followup( + &cc, + CreateInteractionResponseFollowup::default() + .ephemeral(true) + .content("not admin"), + ) + .await + .unwrap(); + } + } + }); + + loop { + match orx.try_recv() { + Ok(Payload::Stdout(x) | Payload::Stderr(x)) => { + dat.push_str(&x); + if dat.len() > 1900 { + let mut i = dat.len() - 1900; + while !dat.is_char_boundary(i) { + i -= 1; + } + dat.drain(0..i); + } + h.edit( + c, + poise::CreateReply::default().content(format!("```ansi\n{dat}\n```")), + ) + .await?; + } + Ok(Payload::Exit(x)) => { + let e = if x.success() { OK } else { CANCEL }; + h.edit( + c, + poise::CreateReply::default() + .content(format!("{e} ```ansi\n{dat}\n```")) + .components(vec![]), + ) + .await?; + k.abort(); + break; + } + Err(ChannelE::Empty | ChannelE::Lagged(_)) => {} + Err(ChannelE::Closed) => panic!(), + } + tokio::time::sleep(std::time::Duration::from_millis(200)).await; + } + + Ok(()) +} |