use super::{Context, Result}; use crate::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 { 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::(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("/root") .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("\u{200b}") .components(vec![CreateActionRow::Buttons(vec![CreateButton::new( format!("{}_ctrl_c", c.id()), ) .label("C^c") .style(ButtonStyle::Danger) .emoji(CANCEL.parse::().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(()) }