Skip to content

Commit 8b6bf69

Browse files
committed
refactor(close): complete refactor of close command
1 parent 6eba9ee commit 8b6bf69

7 files changed

Lines changed: 195 additions & 252 deletions

File tree

rustmail/src/commands/close/common.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
use std::time::Duration;
22

3+
pub fn format_duration(seconds: u64) -> String {
4+
if seconds < 60 {
5+
format!("{}s", seconds)
6+
} else if seconds < 3600 {
7+
format!("{}m", seconds / 60)
8+
} else if seconds < 86400 {
9+
format!("{}h{}m", seconds / 3600, (seconds % 3600) / 60)
10+
} else {
11+
format!("{}d{}h", seconds / 86400, (seconds % 86400) / 3600)
12+
}
13+
}
14+
315
pub fn parse_duration_spec(spec: &str) -> Option<Duration> {
416
if spec.is_empty() {
517
return None;
618
}
19+
720
let mut total: u64 = 0;
821
let mut num: u64 = 0;
922
let mut has_unit_segment = false;
23+
1024
for ch in spec.chars() {
1125
if ch.is_ascii_digit() {
1226
let digit = ch.to_digit(10)? as u64;
@@ -24,13 +38,15 @@ pub fn parse_duration_spec(spec: &str) -> Option<Duration> {
2438
has_unit_segment = true;
2539
}
2640
}
41+
2742
if num > 0 {
2843
if has_unit_segment {
2944
total = total.saturating_add(num);
3045
} else {
31-
total = total.saturating_add(num * 60);
46+
total = total.saturating_add(num);
3247
}
3348
}
49+
3450
if total == 0 {
3551
None
3652
} else {

rustmail/src/commands/close/slash_command/close.rs

Lines changed: 78 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ use crate::prelude::db::*;
44
use crate::prelude::errors::*;
55
use crate::prelude::handlers::*;
66
use crate::prelude::i18n::*;
7+
use crate::prelude::modules::*;
78
use crate::prelude::utils::*;
89
use chrono::Utc;
910
use serenity::FutureExt;
1011
use serenity::all::{
11-
CommandDataOptionValue, CommandInteraction, CommandOptionType, Context, CreateCommand,
12-
CreateCommandOption, GuildId, ResolvedOption, UserId,
12+
Channel, CommandDataOptionValue, CommandInteraction, CommandOptionType, Context, CreateCommand,
13+
CreateCommandOption, GuildId, PermissionOverwriteType, ResolvedOption, RoleId, UserId,
1314
};
1415
use std::collections::HashMap;
1516
use std::sync::Arc;
1617
use std::time::Duration;
17-
use tokio::time::sleep;
1818

1919
pub struct CloseCommand;
2020

@@ -191,16 +191,32 @@ impl RegistrableCommand for CloseCommand {
191191
}
192192

193193
if let Some(delay) = duration {
194+
if let Ok(Some(existing)) = get_scheduled_closure(&thread.id, db_pool).await {
195+
let remaining = existing.close_at - Utc::now().timestamp();
196+
if remaining > 0 {
197+
let old_human = format_duration(remaining as u64);
198+
199+
let mut warn_params = HashMap::new();
200+
warn_params.insert("old_time".to_string(), old_human);
201+
202+
let response = MessageBuilder::system_message(&ctx, &config)
203+
.translated_content(
204+
"close.replacing_existing_closure",
205+
Some(&warn_params),
206+
Some(command.user.id),
207+
command.guild_id.map(|g| g.get()),
208+
)
209+
.await
210+
.to_channel(command.channel_id)
211+
.build_interaction_message_followup()
212+
.await;
213+
214+
let _ = command.create_followup(&ctx.http, response).await;
215+
}
216+
}
217+
194218
let delay_secs = delay.as_secs();
195-
let human = if delay_secs < 60 {
196-
format!("{}s", delay_secs)
197-
} else if delay_secs < 3600 {
198-
format!("{}m", delay_secs / 60)
199-
} else if delay_secs < 86400 {
200-
format!("{}h{}m", delay_secs / 3600, (delay_secs % 3600) / 60)
201-
} else {
202-
format!("{}d{}h", delay_secs / 86400, (delay_secs % 86400) / 3600)
203-
};
219+
let human = format_duration(delay_secs);
204220
let mut params = HashMap::new();
205221
params.insert("time".to_string(), human);
206222

@@ -217,7 +233,7 @@ impl RegistrableCommand for CloseCommand {
217233
.build_interaction_message_followup()
218234
.await;
219235

220-
let _ = command.create_followup(&ctx.http, response).await;
236+
command.create_followup(&ctx.http, response).await
221237
} else {
222238
let response = MessageBuilder::system_message(&ctx, &config)
223239
.translated_content(
@@ -231,18 +247,58 @@ impl RegistrableCommand for CloseCommand {
231247
.build_interaction_message_followup()
232248
.await;
233249

234-
let _ = command.create_followup(&ctx.http, response).await;
250+
command.create_followup(&ctx.http, response).await
235251
};
236252

253+
let closed_by = command.user.id.to_string();
254+
255+
let (category_id, category_name, required_permissions) =
256+
match command.channel_id.to_channel(&ctx.http).await {
257+
Ok(Channel::Guild(guild_channel)) => {
258+
let guild_id = guild_channel.guild_id;
259+
let parent_id = guild_channel.parent_id;
260+
261+
let category_id =
262+
parent_id.map(|id| id.to_string()).unwrap_or_default();
263+
264+
let category_name = if let Some(parent_id) = parent_id {
265+
guild_id
266+
.channels(&ctx.http)
267+
.await
268+
.ok()
269+
.and_then(|channels| {
270+
channels.get(&parent_id).map(|c| c.name.clone())
271+
})
272+
.unwrap_or_default()
273+
} else {
274+
String::new()
275+
};
276+
277+
let guild = guild_id.to_partial_guild(&ctx.http).await.ok();
278+
let everyone_role_id = RoleId::new(guild_id.get());
279+
280+
let mut perms = guild
281+
.and_then(|g| {
282+
g.roles.get(&everyone_role_id).map(|r| r.permissions.bits())
283+
})
284+
.unwrap_or(0u64);
285+
286+
for overwrite in &guild_channel.permission_overwrites {
287+
if let PermissionOverwriteType::Role(_) = overwrite.kind {
288+
let allow = overwrite.allow.bits();
289+
let deny = overwrite.deny.bits();
290+
perms = (perms & !deny) | allow;
291+
}
292+
}
293+
294+
(category_id, category_name, perms)
295+
}
296+
_ => (String::new(), String::new(), 0u64),
297+
};
298+
237299
let thread_id = thread.id.clone();
238300
let close_at = Utc::now().timestamp() + delay.as_secs() as i64;
239301

240-
let closed_by = command.user.id.to_string();
241-
let category_id = get_category_id_from_command(&ctx, &command).await;
242-
let category_name = get_category_name_from_command(&ctx, &command).await;
243-
let required_permissions =
244-
get_required_permissions_channel_from_command(&ctx, &command).await;
245-
246302
if let Err(e) = upsert_scheduled_closure(
247303
&thread_id,
248304
close_at,
@@ -257,103 +313,8 @@ impl RegistrableCommand for CloseCommand {
257313
{
258314
eprintln!("Failed to persist scheduled closure: {e:?}");
259315
}
260-
let channel_id = command.channel_id;
261-
let config_clone = config.clone();
262-
let ctx_clone = ctx.clone();
263-
let user_id_clone = user_id;
264-
let thread_id_for_task = thread_id.clone();
265-
266-
tokio::spawn(async move {
267-
sleep(delay).await;
268-
if let Some(pool) = config_clone.db_pool.as_ref() {
269-
if let Ok(Some(record)) =
270-
get_scheduled_closure(&thread_id_for_task, pool).await
271-
{
272-
if record.close_at <= Utc::now().timestamp() {
273-
let _ = close_thread(
274-
&thread_id_for_task,
275-
&record.closed_by,
276-
&record.category_id,
277-
&record.category_name,
278-
record.required_permissions.parse::<u64>().unwrap_or(0),
279-
pool,
280-
)
281-
.await;
282-
let _ = delete_scheduled_closure(&thread_id_for_task, pool).await;
283-
284-
let community_guild_id =
285-
GuildId::new(config_clone.bot.get_community_guild_id());
286-
287-
let user_still_member = community_guild_id
288-
.member(&ctx_clone.http, user_id_clone)
289-
.await
290-
.is_ok();
291-
292-
if !record.silent && user_still_member {
293-
let _ =
294-
MessageBuilder::system_message(&ctx_clone, &config_clone)
295-
.content(&config_clone.bot.close_message)
296-
.to_user(user_id_clone)
297-
.send(true)
298-
.await;
299-
}
300-
let _ = channel_id.delete(&ctx_clone.http).await;
301-
} else {
302-
let delay2 =
303-
(record.close_at - Utc::now().timestamp()).max(1) as u64;
304-
let config_clone2 = config_clone.clone();
305-
let ctx_clone2 = ctx_clone.clone();
306-
let thread_id_again = thread_id_for_task.clone();
307-
308-
tokio::spawn(async move {
309-
sleep(Duration::from_secs(delay2)).await;
310-
if let Some(pool2) = config_clone2.db_pool.as_ref() {
311-
if let Ok(Some(r2)) =
312-
get_scheduled_closure(&thread_id_again, pool2).await
313-
{
314-
if r2.close_at <= Utc::now().timestamp() {
315-
let _ = close_thread(
316-
&thread_id_again,
317-
&r2.closed_by,
318-
&r2.category_id,
319-
&r2.category_name,
320-
r2.required_permissions
321-
.parse::<u64>()
322-
.unwrap_or(0),
323-
pool2,
324-
)
325-
.await;
326-
let _ = delete_scheduled_closure(
327-
&thread_id_again,
328-
pool2,
329-
)
330-
.await;
331-
let community_guild_id = GuildId::new(
332-
config_clone2.bot.get_community_guild_id(),
333-
);
334-
let user_still_member = community_guild_id
335-
.member(&ctx_clone2.http, user_id_clone)
336-
.await
337-
.is_ok();
338-
if !r2.silent && user_still_member {
339-
let _ = MessageBuilder::system_message(
340-
&ctx_clone2,
341-
&config_clone2,
342-
)
343-
.content(&config_clone2.bot.close_message)
344-
.to_user(user_id_clone)
345-
.send(true)
346-
.await;
347-
}
348-
let _ = channel_id.delete(&ctx_clone2.http).await;
349-
}
350-
}
351-
}
352-
});
353-
}
354-
}
355-
}
356-
});
316+
317+
schedule_one(&ctx, &config, thread_id, close_at);
357318
return Ok(());
358319
}
359320

0 commit comments

Comments
 (0)