Skip to content

Commit c60e22b

Browse files
committed
feat: avoid recently selected members
1 parent 08dcde9 commit c60e22b

2 files changed

Lines changed: 66 additions & 6 deletions

File tree

src/commands/random.rs

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,84 @@ use crate::{
33
Context, Error,
44
};
55
use rand::seq::IteratorRandom;
6-
use serenity::all::{Mentionable as _, RoleId};
6+
use serenity::all::{Mentionable as _, RoleId, UserId};
7+
use std::collections::HashSet;
78

89
#[poise::command(slash_command)]
910
pub async fn random(ctx: Context<'_>) -> Result<(), Error> {
1011
let guild = ctx.guild_id().ok_or("No guild id")?;
1112
let members = guild.members(ctx.http(), None, None).await?;
12-
13-
let selected: Vec<_> = members
13+
let eligible_members: Vec<_> = members // Filtering out bots and other ineligible members
1414
.into_iter()
1515
.filter(|m| {
1616
!m.user.bot
1717
&& (m.roles.contains(&RoleId::new(FIRST_YEAR_ROLE_ID))
1818
|| m.roles.contains(&RoleId::new(SECOND_YEAR_ROLE_ID)))
1919
})
20-
.sample(&mut rand::rng(), 5);
20+
.collect();
21+
22+
if eligible_members.is_empty() {
23+
ctx.say("No eligible members found.").await?;
24+
return Ok(());
25+
}
26+
27+
let recent_picks = { // Accessing recently picked members to avoid repetition
28+
let data = ctx.data();
29+
let recent_random_picks = data.recent_random_picks.lock().unwrap();
30+
recent_random_picks.clone()
31+
};
32+
33+
let available_members: Vec<_> = eligible_members // Members who haven't been picked recently
34+
.iter()
35+
.filter(|member| !recent_picks.contains(&member.user.id))
36+
.collect();
37+
38+
/* Since ThreadRng is not Send, keeping it alive across an .await causes command future to become non-Send.
39+
So, we are enclosing the selection in it's own scope so the ThreadRng is dropped before we hit any .await,
40+
allowing the command future to remain Send
41+
*/
42+
43+
let selected = {
44+
let mut rng = rand::rng();
45+
46+
let mut selected: Vec<_> = available_members
47+
.into_iter()
48+
.sample(&mut rng, 5);
49+
50+
if selected.len() < 5 { // If not enough members are available, fetch recently picked members
51+
let remaining_needed = 5 - selected.len();
52+
53+
let selected_ids: HashSet<UserId> =
54+
selected.iter().map(|m| m.user.id).collect();
55+
56+
let additional: Vec<_> = eligible_members
57+
.iter()
58+
.filter(|m| !selected_ids.contains(&m.user.id))
59+
.sample(&mut rng, remaining_needed);
60+
61+
selected.extend(additional);
62+
}
63+
selected
64+
};
2165

2266
let ping_message = selected
2367
.iter()
2468
.map(|m| m.user.mention().to_string())
2569
.collect::<Vec<_>>()
2670
.join("\n");
2771

28-
ctx.say(format!("Pinging 5 members: {}", ping_message))
72+
{
73+
let data = ctx.data();
74+
let mut recent_random_picks = data.recent_random_picks.lock().unwrap();
75+
recent_random_picks.extend(selected.iter().map(|m| m.user.id)); // Adding selected members to recently picked set
76+
77+
let eligible_ids: HashSet<UserId> = eligible_members.iter().map(|m| m.user.id).collect();
78+
recent_random_picks.retain(|user_id| eligible_ids.contains(user_id));
79+
if recent_random_picks.len() >= eligible_ids.len() { // If all eligible members have been picked atleast once, clear the recently picked set
80+
recent_random_picks.clear();
81+
}
82+
}
83+
ctx.say(format!("Pinging {} members: {}", selected.len(), ping_message))
2984
.await?;
3085

3186
Ok(())

src/main.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ use serenity::{
4040
use trace::{setup_tracing, ReloadHandle};
4141
use tracing::{debug, info, instrument};
4242

43-
use std::collections::HashMap;
43+
use std::{
44+
collections::{HashMap, HashSet},
45+
sync::{Arc, Mutex},
46+
};
4447

4548
type Error = Box<dyn std::error::Error + Send + Sync>;
4649
type Context<'a> = PoiseContext<'a, Data, Error>;
@@ -49,6 +52,7 @@ type Context<'a> = PoiseContext<'a, Data, Error>;
4952
#[derive(Clone)]
5053
struct Data {
5154
reaction_roles: HashMap<ReactionType, RoleId>,
55+
recent_random_picks: Arc<Mutex<HashSet<UserId>>>,
5256
log_reload_handle: ReloadHandle,
5357
graphql_client: GraphQLClient,
5458
}
@@ -58,6 +62,7 @@ impl Data {
5862
fn new(reload_handle: ReloadHandle, root_url: String, api_key: String) -> Self {
5963
Data {
6064
reaction_roles: HashMap::new(),
65+
recent_random_picks: Arc::new(Mutex::new(HashSet::new())),
6166
log_reload_handle: reload_handle,
6267
graphql_client: GraphQLClient::new(root_url, api_key),
6368
}

0 commit comments

Comments
 (0)