Skip to content

Commit 4e43c02

Browse files
committed
feat: Add /nuke
1 parent 6e52147 commit 4e43c02

4 files changed

Lines changed: 150 additions & 1 deletion

File tree

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pi-bot/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ anyhow = "1.0.100"
88
chrono = "0.4.42"
99
common = { path = "../common/" }
1010
dotenv = { workspace = true }
11+
indoc = "2.0.7"
1112
log = { workspace = true, features = ["max_level_debug", "release_max_level_info"] }
1213
num-bigint = "0.4.6"
1314
poise = { git = "https://github.com/Nydauron/poise.git", branch = "feat/burstable-cooldowns", version = "0.6.1" }

pi-bot/src/discord/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub fn all_commands() -> Vec<Command> {
2727
general::coach(),
2828
general::info(),
2929
staff::slowmode(),
30+
staff::nuke(),
3031
sync::sync(),
3132
]
3233
}

pi-bot/src/discord/commands/staff.rs

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1-
use poise::serenity_prelude::{EditChannel, GuildChannel, Mentionable};
1+
use indoc::formatdoc;
2+
use poise::{
3+
CreateReply,
4+
serenity_prelude::{
5+
ButtonStyle, Colour, ComponentInteractionDataKind, CreateActionRow, CreateButton,
6+
CreateEmbed, CreateInteractionResponse, CreateInteractionResponseMessage, EditChannel,
7+
GetMessages, GuildChannel, Mentionable,
8+
futures::future::{Either, select},
9+
},
10+
};
11+
use std::{pin::pin, time::Duration};
12+
use tokio::time::Instant;
213

314
use crate::discord::{Context, Error};
415

@@ -57,3 +68,129 @@ pub async fn remove(
5768
.await?;
5869
Ok(())
5970
}
71+
72+
/// Staff command. Nukes a certain amount of messages.
73+
#[poise::command(
74+
slash_command,
75+
guild_only,
76+
default_member_permissions = "MANAGE_MESSAGES",
77+
required_bot_permissions = "MANAGE_MESSAGES"
78+
)]
79+
pub async fn nuke(
80+
ctx: Context<'_>,
81+
#[description = "The amount of messages to nuke."]
82+
#[min = 1]
83+
#[max = 100]
84+
count: u8,
85+
) -> Result<(), Error> {
86+
let channel = ctx.guild_channel().await.unwrap();
87+
let messages_to_delete = channel
88+
.messages(ctx.http(), GetMessages::new().limit(count))
89+
.await?;
90+
91+
const NUKE_CANCEL_BUTTON_ID: &str = "nuke-cancel";
92+
const COUNTDOWN_START_SECS: u64 = 10;
93+
let cancel_button = CreateButton::new(NUKE_CANCEL_BUTTON_ID)
94+
.label("Cancel")
95+
.style(ButtonStyle::Danger);
96+
let embed = CreateEmbed::new()
97+
.title("NUKE COMMAND PANEL")
98+
.color(Colour::RED)
99+
.description(formatdoc!(
100+
"
101+
{} messages will be deleted from {} in **{} seconds**...
102+
103+
To stop this nuke, press the red button below!
104+
",
105+
count,
106+
channel.mention(),
107+
COUNTDOWN_START_SECS
108+
));
109+
let components = vec![CreateActionRow::Buttons(vec![cancel_button])];
110+
let reply = CreateReply::new()
111+
.embed(embed.clone())
112+
.components(components.clone());
113+
114+
let reply_handler = ctx.send(reply.clone()).await?;
115+
let reply_handler_countdown = reply_handler.clone();
116+
let countdown = async move {
117+
let reply_handler = reply_handler_countdown;
118+
let embed = embed;
119+
120+
let mut ticker = tokio::time::interval_at(
121+
Instant::now() + Duration::from_secs(1),
122+
Duration::from_secs(1),
123+
);
124+
ticker.tick().await;
125+
for i in (1..COUNTDOWN_START_SECS).rev() {
126+
let updated_description = formatdoc!(
127+
"
128+
{} messages will be deleted from {} in **{} second{}**...
129+
130+
To stop this nuke, press the red button below!
131+
",
132+
count,
133+
channel.mention(),
134+
i,
135+
if i > 1 { "s" } else { "" }
136+
);
137+
reply_handler
138+
.edit(
139+
ctx,
140+
CreateReply::new().embed(embed.clone().description(updated_description)),
141+
)
142+
.await?;
143+
ticker.tick().await;
144+
}
145+
146+
Ok::<(), Error>(())
147+
};
148+
149+
let button_interaction = reply_handler
150+
.message()
151+
.await?
152+
.await_component_interaction(ctx)
153+
.into_future();
154+
match select(pin!(countdown), button_interaction).await {
155+
Either::Left((countdown, _)) => {
156+
countdown?;
157+
}
158+
Either::Right((component_interaction, _)) => {
159+
if let Some(component_interaction) = component_interaction
160+
&& matches!(
161+
component_interaction.data.kind,
162+
ComponentInteractionDataKind::Button
163+
)
164+
&& component_interaction.data.custom_id == NUKE_CANCEL_BUTTON_ID
165+
{
166+
component_interaction
167+
.create_response(
168+
ctx.http(),
169+
CreateInteractionResponse::UpdateMessage(
170+
CreateInteractionResponseMessage::new()
171+
.content("Message nuke cancelled")
172+
.embeds(vec![])
173+
.components(vec![]),
174+
),
175+
)
176+
.await?;
177+
}
178+
return Ok(());
179+
}
180+
}
181+
182+
let reply = CreateReply::new()
183+
.content(format!("Now nuking {} messages ...", count))
184+
.components(vec![]);
185+
reply_handler.edit(ctx, reply).await?;
186+
187+
for message in messages_to_delete {
188+
message.delete(ctx.http()).await?;
189+
}
190+
191+
let reply = CreateReply::new()
192+
.content(format!("Nuked {} messages.", count))
193+
.components(vec![]);
194+
reply_handler.edit(ctx, reply).await?;
195+
Ok(())
196+
}

0 commit comments

Comments
 (0)