Skip to content

Commit 6b639de

Browse files
committed
fix(reminder): multiple role can be added to reminders
1 parent 9249040 commit 6b639de

8 files changed

Lines changed: 248 additions & 213 deletions

File tree

crates/rustmail/src/commands/add_reminder/common.rs

Lines changed: 85 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ pub async fn send_register_confirmation_from_message(
1818
msg: &Message,
1919
config: &Config,
2020
trigger_timestamp: i64,
21+
target_roles: &Option<String>,
2122
) {
2223
let mut params = HashMap::new();
23-
params.insert("time".to_string(), format!("<t:{}:F>", trigger_timestamp));
24+
params.insert("time".to_string(), format!("<t:{}:t>", trigger_timestamp));
2425
params.insert(
2526
"remaining_time".to_string(),
2627
format!("<t:{}:R>", trigger_timestamp),
@@ -30,33 +31,33 @@ pub async fn send_register_confirmation_from_message(
3031
params.insert("content".to_string(), reminder_content.to_string());
3132
}
3233

33-
if !reminder_content.is_empty() {
34-
let _ = MessageBuilder::system_message(&ctx, &config)
35-
.translated_content(
36-
"reminder.registered_with_content",
37-
Some(&params),
38-
None,
39-
None,
40-
)
41-
.await
42-
.to_channel(msg.channel_id)
43-
.footer(format!("{}: {}", "ID", reminder_id))
44-
.send(true)
45-
.await;
34+
let has_roles = if let Some(roles_str) = target_roles {
35+
let role_mentions: String = roles_str
36+
.split(',')
37+
.filter_map(|s| s.trim().parse::<u64>().ok())
38+
.map(|id| format!("<@&{}>", id))
39+
.collect::<Vec<_>>()
40+
.join(", ");
41+
params.insert("roles".to_string(), role_mentions);
42+
true
4643
} else {
47-
let _ = MessageBuilder::system_message(&ctx, &config)
48-
.translated_content(
49-
"reminder.registered_without_content",
50-
Some(&params),
51-
None,
52-
None,
53-
)
54-
.await
55-
.to_channel(msg.channel_id)
56-
.footer(format!("{}: {}", "ID", reminder_id))
57-
.send(true)
58-
.await;
59-
}
44+
false
45+
};
46+
47+
let key = match (has_roles, !reminder_content.is_empty()) {
48+
(true, true) => "reminder.registered_with_content_roles",
49+
(true, false) => "reminder.registered_without_content_roles",
50+
(false, true) => "reminder.registered_with_content",
51+
(false, false) => "reminder.registered_without_content",
52+
};
53+
54+
let _ = MessageBuilder::system_message(&ctx, &config)
55+
.translated_content(key, Some(&params), None, None)
56+
.await
57+
.to_channel(msg.channel_id)
58+
.footer(format!("{}: {}", "ID", reminder_id))
59+
.send(true)
60+
.await;
6061
}
6162

6263
pub async fn send_register_confirmation_from_command(
@@ -66,9 +67,10 @@ pub async fn send_register_confirmation_from_command(
6667
command: &CommandInteraction,
6768
config: &Config,
6869
trigger_timestamp: i64,
70+
target_roles: &Option<String>,
6971
) {
7072
let mut params = HashMap::new();
71-
params.insert("time".to_string(), format!("<t:{}:F>", trigger_timestamp));
73+
params.insert("time".to_string(), format!("<t:{}:t>", trigger_timestamp));
7274
params.insert(
7375
"remaining_time".to_string(),
7476
format!("<t:{}:R>", trigger_timestamp),
@@ -78,33 +80,33 @@ pub async fn send_register_confirmation_from_command(
7880
params.insert("content".to_string(), reminder_content.to_string());
7981
}
8082

81-
if !reminder_content.is_empty() {
82-
let _ = MessageBuilder::system_message(&ctx, &config)
83-
.translated_content(
84-
"reminder.registered_with_content",
85-
Some(&params),
86-
None,
87-
None,
88-
)
89-
.await
90-
.to_channel(command.channel_id)
91-
.footer(format!("{}: {}", "ID", reminder_id))
92-
.send_interaction_followup(&command, true)
93-
.await;
83+
let has_roles = if let Some(roles_str) = target_roles {
84+
let role_mentions: String = roles_str
85+
.split(',')
86+
.filter_map(|s| s.trim().parse::<u64>().ok())
87+
.map(|id| format!("<@&{}>", id))
88+
.collect::<Vec<_>>()
89+
.join(", ");
90+
params.insert("roles".to_string(), role_mentions);
91+
true
9492
} else {
95-
let _ = MessageBuilder::system_message(&ctx, &config)
96-
.translated_content(
97-
"reminder.registered_without_content",
98-
Some(&params),
99-
None,
100-
None,
101-
)
102-
.await
103-
.to_channel(command.channel_id)
104-
.footer(format!("{}: {}", "ID", reminder_id))
105-
.send_interaction_followup(&command, true)
106-
.await;
107-
}
93+
false
94+
};
95+
96+
let key = match (has_roles, !reminder_content.is_empty()) {
97+
(true, true) => "reminder.registered_with_content_roles",
98+
(true, false) => "reminder.registered_without_content_roles",
99+
(false, true) => "reminder.registered_with_content",
100+
(false, false) => "reminder.registered_without_content",
101+
};
102+
103+
let _ = MessageBuilder::system_message(&ctx, &config)
104+
.translated_content(key, Some(&params), None, None)
105+
.await
106+
.to_channel(command.channel_id)
107+
.footer(format!("{}: {}", "ID", reminder_id))
108+
.send_interaction_followup(&command, true)
109+
.await;
108110
}
109111

110112
pub fn spawn_reminder(
@@ -250,34 +252,43 @@ async fn get_targeted_mentions(
250252
}
251253
};
252254

253-
let mut user_ids_with_roles: HashSet<UserId> = HashSet::new();
254-
255-
for role_id in &role_ids {
256-
let role_id_obj = RoleId::new(*role_id);
257-
for member in &members {
258-
if member.roles.contains(&role_id_obj) {
259-
user_ids_with_roles.insert(member.user.id);
260-
}
261-
}
262-
}
263-
264-
let mut opted_out_users: HashSet<u64> = HashSet::new();
265-
255+
let mut optouts_by_role: HashMap<u64, HashSet<u64>> = HashMap::new();
266256
for role_id in &role_ids {
267257
match get_optouts_for_role(guild_id as i64, *role_id as i64, pool).await {
268258
Ok(optouts) => {
269-
for user_id in optouts {
270-
opted_out_users.insert(user_id as u64);
271-
}
259+
optouts_by_role.insert(*role_id, optouts.into_iter().map(|id| id as u64).collect());
272260
}
273261
Err(e) => {
274262
eprintln!("Failed to get optouts for role {}: {}", role_id, e);
263+
optouts_by_role.insert(*role_id, HashSet::new());
275264
}
276265
}
277266
}
278267

279-
user_ids_with_roles
280-
.into_iter()
281-
.filter(|user_id| !opted_out_users.contains(&user_id.get()))
282-
.collect()
268+
let mut users_to_mention: Vec<UserId> = Vec::new();
269+
270+
for member in &members {
271+
let user_target_roles: Vec<u64> = role_ids
272+
.iter()
273+
.filter(|&&role_id| member.roles.contains(&RoleId::new(role_id)))
274+
.copied()
275+
.collect();
276+
277+
if user_target_roles.is_empty() {
278+
continue;
279+
}
280+
281+
let has_subscribed_role = user_target_roles.iter().any(|role_id| {
282+
!optouts_by_role
283+
.get(role_id)
284+
.map(|optouts| optouts.contains(&member.user.id.get()))
285+
.unwrap_or(false)
286+
});
287+
288+
if has_subscribed_role {
289+
users_to_mention.push(member.user.id);
290+
}
291+
}
292+
293+
users_to_mention
283294
}

crates/rustmail/src/commands/add_reminder/slash_command/add_reminder.rs

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ impl RegistrableCommand for AddReminderCommand {
205205
trigger_time: trigger_timestamp,
206206
created_at: now.timestamp(),
207207
completed: false,
208-
target_roles,
208+
target_roles: target_roles.clone(),
209209
};
210210

211211
let reminder_id = match insert_reminder(&reminder, pool).await {
@@ -223,6 +223,7 @@ impl RegistrableCommand for AddReminderCommand {
223223
&command,
224224
&config,
225225
trigger_timestamp,
226+
&target_roles,
226227
)
227228
.await;
228229

@@ -256,31 +257,35 @@ async fn resolve_role_names_to_ids(
256257
};
257258

258259
let mention_regex = Regex::new(r"<@&(\d+)>").unwrap();
259-
260-
let role_parts: Vec<&str> = roles_str.split(',').map(|s| s.trim()).collect();
261260
let mut role_ids: Vec<u64> = Vec::new();
262261

263-
for role_part in role_parts {
264-
if role_part.is_empty() {
265-
continue;
266-
}
267-
268-
if let Some(caps) = mention_regex.captures(role_part) {
269-
if let Some(id_match) = caps.get(1) {
270-
if let Ok(id) = id_match.as_str().parse::<u64>() {
271-
if guild.roles.contains_key(&RoleId::new(id)) {
272-
role_ids.push(id);
273-
}
262+
for caps in mention_regex.captures_iter(roles_str) {
263+
if let Some(id_match) = caps.get(1) {
264+
if let Ok(id) = id_match.as_str().parse::<u64>() {
265+
if guild.roles.contains_key(&RoleId::new(id)) && !role_ids.contains(&id) {
266+
role_ids.push(id);
274267
}
275268
}
276-
} else {
269+
}
270+
}
271+
272+
if role_ids.is_empty() {
273+
let role_parts: Vec<&str> = roles_str
274+
.split(|c: char| c == ',' || c.is_whitespace())
275+
.map(|s| s.trim())
276+
.filter(|s| !s.is_empty())
277+
.collect();
278+
279+
for role_part in role_parts {
277280
let role_name_lower = role_part.to_lowercase();
278281
if let Some(role) = guild
279282
.roles
280283
.values()
281284
.find(|r| r.name.to_lowercase() == role_name_lower)
282285
{
283-
role_ids.push(role.id.get());
286+
if !role_ids.contains(&role.id.get()) {
287+
role_ids.push(role.id.get());
288+
}
284289
}
285290
}
286291
}

0 commit comments

Comments
 (0)