silly goober bot
1use crate::types::Context;
2use color_eyre::Result;
3use poise::serenity_prelude::all::{EditMember, Timestamp, User};
4use regex::Regex;
5
6#[poise::command(slash_command, guild_only, required_permissions = "MODERATE_MEMBERS")]
7pub async fn timeout(
8 ctx: Context<'_>,
9 user: User,
10 #[description = "how long the time out is for. (5s, 2m, 12h, 3d, 2w)"] duration: String,
11 reason: Option<String>,
12) -> Result<()> {
13 let guild = ctx
14 .serenity_context()
15 .http
16 .get_guild(ctx.guild_id().unwrap())
17 .await?;
18
19 let timeout_max_value = 2419200;
20
21 let user_member = guild.member(ctx, user.id).await.unwrap();
22 let bot_member = guild.member(ctx, ctx.framework().bot_id).await.unwrap();
23 let author_member = guild.member(ctx, ctx.author()).await.unwrap();
24
25 let user_highest_role = user_member
26 .roles
27 .iter()
28 .filter_map(|role| guild.roles.get(role))
29 .max_by_key(|role| role.position);
30
31 let author_highest_role = author_member
32 .roles
33 .iter()
34 .filter_map(|role| guild.roles.get(role))
35 .max_by_key(|role| role.position);
36
37 let bot_highest_role = bot_member
38 .roles
39 .iter()
40 .filter_map(|role| guild.roles.get(role))
41 .max_by_key(|role| role.position);
42
43 if guild
44 .user_permissions_in(
45 &ctx.guild_channel().await.unwrap(),
46 &guild.member(ctx, user.id).await.unwrap(),
47 )
48 .administrator()
49 {
50 ctx.say("Target user has permission: ``Administrator``")
51 .await?;
52 return Ok(());
53 }
54
55 if !guild
56 .user_permissions_in(
57 &ctx.guild_channel().await.unwrap(),
58 &guild.member(ctx, ctx.framework().bot_id).await.unwrap(),
59 )
60 .moderate_members()
61 {
62 ctx.say("Bot missing permission: ``Moderate Members``")
63 .await?;
64 return Ok(());
65 }
66
67 if author_highest_role < user_highest_role {
68 ctx.say("User has higher role then you.").await?;
69 return Ok(());
70 }
71
72 if bot_highest_role < user_highest_role {
73 ctx.say("User has higher role then bot!").await?;
74 return Ok(());
75 }
76
77 let Some(seconds) = parse_duration(&duration) else {
78 ctx.say("Invalid duration! (Valid durations: 5s, 2m, 12h, 3d, 2w)")
79 .await?;
80 return Ok(());
81 };
82
83 if seconds > timeout_max_value {
84 ctx.say("Duration too long! (Max duration: 28d)").await?;
85 return Ok(());
86 }
87
88 let timeout_until =
89 Timestamp::from_unix_timestamp(Timestamp::now().unix_timestamp() + seconds as i64)?;
90
91 guild
92 .member(ctx, user.id)
93 .await?
94 .edit(
95 ctx,
96 EditMember::new()
97 .disable_communication_until(timeout_until.to_string())
98 .audit_log_reason(&reason.unwrap_or("No reason provided.".to_string())),
99 )
100 .await?;
101
102 ctx.say(format!("Timed out <@{}> for {}!", user.id, duration))
103 .await?;
104
105 Ok(())
106}
107
108fn parse_duration(input: &str) -> Option<u64> {
109 let re = Regex::new(r"(\d+)([smhdw])").unwrap();
110
111 if let Some(caps) = re.captures(input) {
112 let value: u64 = caps[1].parse().ok()?;
113 let unit = &caps[2];
114
115 let seconds = match unit {
116 "s" => value,
117 "m" => value * 60,
118 "h" => value * 3600,
119 "d" => value * 86400,
120 "w" => value * 604800,
121 _ => return None,
122 };
123
124 Some(seconds)
125 } else {
126 None
127 }
128}