@@ -3,29 +3,84 @@ use crate::{
33 Context , Error ,
44} ;
55use 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) ]
910pub 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 ( ( ) )
0 commit comments