Skip to content

Commit 028558e

Browse files
committed
feat: Add /lock and /unlock channel commands
1 parent 897c4c8 commit 028558e

3 files changed

Lines changed: 194 additions & 7 deletions

File tree

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ pub fn all_commands() -> Vec<Command> {
2929
staff::slowmode(),
3030
staff::nuke(),
3131
staff::mute(),
32+
staff::lock(),
33+
staff::unlock(),
3234
sync::sync(),
3335
]
3436
}

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

Lines changed: 177 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,23 @@ use indoc::formatdoc;
22
use poise::{
33
ChoiceParameter, CreateReply,
44
serenity_prelude::{
5-
ButtonStyle, Colour, ComponentInteractionDataKind, CreateActionRow, CreateButton,
6-
CreateEmbed, CreateInteractionResponse, CreateInteractionResponseMessage, EditChannel,
7-
FormattedTimestamp, FormattedTimestampStyle, GetMessages, GuildChannel, Member,
8-
Mentionable, Timestamp,
5+
ButtonStyle, ChannelType, Colour, ComponentInteractionDataKind, CreateActionRow,
6+
CreateButton, CreateEmbed, CreateInteractionResponse, CreateInteractionResponseMessage,
7+
EditChannel, FormattedTimestamp, FormattedTimestampStyle, GetMessages, GuildChannel,
8+
Member, Mentionable, PermissionOverwrite, PermissionOverwriteType, Permissions, Timestamp,
99
futures::future::{Either, select},
1010
},
1111
};
1212
use std::{pin::pin, time::Duration};
1313
use tokio::time::Instant;
1414

15-
use crate::discord::{Context, Error, utils::ROLE_MUTED};
15+
use crate::discord::{
16+
Context, Error,
17+
utils::{
18+
CATEGORY_BETA, CATEGORY_COMMUNITY, CATEGORY_STAFF, EMOJI_LOADING, ROLE_BOTS, ROLE_EVERYONE,
19+
ROLE_MUTED, ROLE_STAFF, ROLE_VIP,
20+
},
21+
};
1622

1723
/// Manages slowmode for a channel.
1824
#[poise::command(
@@ -306,3 +312,169 @@ pub async fn mute(
306312
}
307313
Ok(())
308314
}
315+
316+
/// Staff command. Locks a channel, preventing members from sending messages.
317+
#[poise::command(
318+
slash_command,
319+
guild_only,
320+
default_member_permissions = "MANAGE_CHANNELS",
321+
required_bot_permissions = "MANAGE_CHANNELS"
322+
)]
323+
pub async fn lock(ctx: Context<'_>) -> Result<(), Error> {
324+
let reply_handler = ctx
325+
.reply(format!("{} Attempting to lock channel ...", EMOJI_LOADING))
326+
.await?;
327+
328+
let mut channel = ctx.guild_channel().await.unwrap();
329+
let category = channel.parent_id;
330+
let is_priviledged_channel = match category {
331+
None => false,
332+
Some(category_id) => {
333+
let category = category_id
334+
.to_channel(ctx.http())
335+
.await
336+
.map(|channel| channel.guild())
337+
.unwrap();
338+
category.is_some_and(|category| {
339+
matches!(category.kind, ChannelType::Category)
340+
&& [CATEGORY_BETA, CATEGORY_STAFF, CATEGORY_COMMUNITY]
341+
.contains(&category.name.as_str())
342+
})
343+
}
344+
};
345+
if is_priviledged_channel {
346+
reply_handler
347+
.edit(
348+
ctx,
349+
CreateReply::new().content(
350+
"This command is not suitable for this channel because of its category.",
351+
),
352+
)
353+
.await?;
354+
return Ok(());
355+
}
356+
357+
let roles = channel.guild_id.roles(ctx.http()).await?;
358+
let everyone_role = roles
359+
.values()
360+
.find(|role| role.name == ROLE_EVERYONE)
361+
.ok_or(format!("Could not find `{}` role", ROLE_EVERYONE))?;
362+
let staff_role = roles
363+
.values()
364+
.find(|role| role.name == ROLE_STAFF)
365+
.ok_or(format!("Could not find `@{}` role", ROLE_STAFF))?;
366+
let vip_role = roles
367+
.values()
368+
.find(|role| role.name == ROLE_VIP)
369+
.ok_or(format!("Could not find `@{}` role", ROLE_VIP))?;
370+
let bot_role = roles
371+
.values()
372+
.find(|role| role.name == ROLE_BOTS)
373+
.ok_or(format!("Could not find `@{}` role", ROLE_BOTS))?;
374+
channel
375+
.edit(
376+
ctx.http(),
377+
EditChannel::new().permissions([
378+
PermissionOverwrite {
379+
allow: Permissions::READ_MESSAGE_HISTORY,
380+
deny: Permissions::ADD_REACTIONS | Permissions::SEND_MESSAGES,
381+
kind: PermissionOverwriteType::Role(everyone_role.id),
382+
},
383+
PermissionOverwrite {
384+
allow: Permissions::ADD_REACTIONS
385+
| Permissions::SEND_MESSAGES
386+
| Permissions::READ_MESSAGE_HISTORY,
387+
deny: Permissions::empty(),
388+
kind: PermissionOverwriteType::Role(staff_role.id),
389+
},
390+
PermissionOverwrite {
391+
allow: Permissions::ADD_REACTIONS
392+
| Permissions::SEND_MESSAGES
393+
| Permissions::READ_MESSAGE_HISTORY,
394+
deny: Permissions::empty(),
395+
kind: PermissionOverwriteType::Role(vip_role.id),
396+
},
397+
PermissionOverwrite {
398+
allow: Permissions::ADD_REACTIONS
399+
| Permissions::SEND_MESSAGES
400+
| Permissions::READ_MESSAGE_HISTORY,
401+
deny: Permissions::empty(),
402+
kind: PermissionOverwriteType::Role(bot_role.id),
403+
},
404+
]),
405+
)
406+
.await?;
407+
408+
reply_handler
409+
.edit(
410+
ctx,
411+
CreateReply::new().content("Locked the channel to public access."),
412+
)
413+
.await?;
414+
Ok(())
415+
}
416+
417+
/// Staff command. Unlocks a channel, allowing members to speak after the channel was originally locked.
418+
#[poise::command(
419+
slash_command,
420+
guild_only,
421+
default_member_permissions = "MANAGE_CHANNELS",
422+
required_bot_permissions = "MANAGE_CHANNELS"
423+
)]
424+
pub async fn unlock(ctx: Context<'_>) -> Result<(), Error> {
425+
let reply_handler = ctx
426+
.reply(format!(
427+
"{} Attempting to unlock channel ...",
428+
EMOJI_LOADING
429+
))
430+
.await?;
431+
432+
let mut channel = ctx.guild_channel().await.unwrap();
433+
let category = match channel.parent_id {
434+
None => None,
435+
Some(category_id) => {
436+
let category = category_id
437+
.to_channel(ctx.http())
438+
.await
439+
.map(|channel| channel.guild())
440+
.unwrap();
441+
category.and_then(|category| match category.kind {
442+
ChannelType::Category => Some(category),
443+
_ => None,
444+
})
445+
}
446+
};
447+
let roles = channel.guild_id.roles(ctx.http()).await?;
448+
let everyone_role = roles
449+
.values()
450+
.find(|role| role.name == ROLE_EVERYONE)
451+
.ok_or(format!("Could not find `{}` role", ROLE_EVERYONE))?;
452+
453+
if let Some(category) = category
454+
&& [CATEGORY_BETA, CATEGORY_STAFF, CATEGORY_COMMUNITY].contains(&category.name.as_str())
455+
{
456+
reply_handler
457+
.edit(
458+
ctx,
459+
CreateReply::new().content(
460+
"This command is not suitable for this channel because of its category.",
461+
),
462+
)
463+
.await?;
464+
return Ok(());
465+
}
466+
channel
467+
.edit(
468+
ctx.http(),
469+
EditChannel::new().permissions([PermissionOverwrite {
470+
allow: Permissions::empty(),
471+
deny: Permissions::empty(),
472+
kind: PermissionOverwriteType::Role(everyone_role.id),
473+
}]),
474+
)
475+
.await?;
476+
reply_handler.edit(ctx,CreateReply::new().
477+
content("Unlocked the channel to public access. Please check if permissions need to be synced.")
478+
).await?;
479+
Ok(())
480+
}

pi-bot/src/discord/utils.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,22 @@ use poise::serenity_prelude::Member;
22

33
use crate::discord::{Context, Error};
44

5-
static ROLE_STAFF: &str = "Staff";
6-
static ROLE_VIP: &str = "VIP";
5+
// TODO: These names should be used to only fetch the initial roles or channel/categeory names.
6+
// If we rely on these names to find assets on Discord, accidental changes on Discord can lead to
7+
// Pi-Bot breaking.
8+
//
9+
// We should store the snowflake IDs of each item here inside of the database.
10+
//
11+
// It would also make identification easier as we can just compare snowflake IDs
12+
pub static ROLE_STAFF: &str = "Staff";
13+
pub static ROLE_VIP: &str = "VIP";
14+
pub static ROLE_BOTS: &str = "Bots";
715
pub static ROLE_MUTED: &str = "Muted";
16+
pub static ROLE_EVERYONE: &str = "@everyone";
17+
18+
pub static CATEGORY_BETA: &str = "beta";
19+
pub static CATEGORY_STAFF: &str = "staff";
20+
pub static CATEGORY_COMMUNITY: &str = "community";
821

922
pub static EMOJI_LOADING: &str = "<a:loading:1409087568313712731>";
1023

0 commit comments

Comments
 (0)