diff --git a/src/ldap.rs b/src/ldap.rs index 7dce6c243482a85daab43152fb355cf691ae3049..0b598f870e4850e13eb9650e33ff88e213973a01 100644 --- a/src/ldap.rs +++ b/src/ldap.rs @@ -11,7 +11,7 @@ pub struct LDAPUser { pub fn ldap_search(username: &str) -> Option<LDAPUser> { let settings = LdapConnSettings::new().set_no_tls_verify(true); - let ldap = + let mut ldap = LdapConn::with_settings(settings, &CONFIG.bind_address).expect("Unable to connect to LDAP"); ldap.simple_bind( "cn=ucc-discord-bot,cn=Users,dc=ad,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au", @@ -31,7 +31,7 @@ pub fn ldap_search(username: &str) -> Option<LDAPUser> { .success() .expect("LDAP search error"); if rs.is_empty() { - return None + return None; } let result = SearchEntry::construct(rs[0].clone()).attrs; Some(LDAPUser { @@ -44,9 +44,9 @@ pub fn ldap_search(username: &str) -> Option<LDAPUser> { .expect("LDAP failed to get 'displayName' field") .join(""), when_created: "".to_string(), // result - // .get("whenCreated") - // .expect("LDAP failed to get 'whenCreated' field") - // .join(""), + // .get("whenCreated") + // .expect("LDAP failed to get 'whenCreated' field") + // .join(""), login_shell: result .get("loginShell") .expect("LDAP failed to get 'loginShell' field") diff --git a/src/reaction_roles.rs b/src/reaction_roles.rs index fd2b5be3cf2dc163900e9fa3dd99530cb8debffe..1a0a50fa4099f7b321cfb807f03adba412ecd985 100644 --- a/src/reaction_roles.rs +++ b/src/reaction_roles.rs @@ -1,6 +1,5 @@ use crate::config::{ReactRoleMap, CONFIG}; use crate::util::{get_react_from_string, get_string_from_react}; -use rayon::prelude::*; use serenity::{ client::Context, model::{channel::Message, channel::Reaction, id::RoleId, id::UserId}, @@ -198,21 +197,17 @@ async fn get_all_role_reaction_message(ctx: &Context) -> Vec<(Message, &'static let channels = ctx.http.get_channels(*guild.id.as_u64()).await.unwrap(); info!(" Find role-react message: channels determined"); let http = ctx.http.clone(); - channels - .par_iter() - .flat_map(|channel| { - // since we don't know which channels the messages are in, we check every combination - // of message and channel and ignore the bad matches using .ok() and .filter_map() - let h = http.clone(); // thread-local copy - CONFIG - .react_role_messages - .par_iter() - .filter_map(move |rrm| async { - h.get_message(*channel.id.as_u64(), *rrm.message.as_u64()) - .await - .ok() - .map(|m| (m, &rrm.mapping)) - }) - }) - .collect() + let mut v = Vec::new(); + for channel in channels { + for reaction in CONFIG.react_role_messages.iter() { + if let Some(m) = http + .get_message(*channel.id.as_u64(), *reaction.message.as_u64()) + .await + .ok() + { + v.push((m, &reaction.mapping)) + } + } + } + v } diff --git a/src/serenity_handler.rs b/src/serenity_handler.rs index 0cbc512573c22de7f39e61a73e9323422029c86e..7afe70710a59d5ee530f08aaa49b4d0752c44083 100644 --- a/src/serenity_handler.rs +++ b/src/serenity_handler.rs @@ -6,7 +6,7 @@ use serenity::{ utils::MessageBuilder, }; -use rand::seq::SliceRandom; +// use rand::seq::SliceRandom; use crate::config::CONFIG; use crate::ldap; @@ -35,9 +35,8 @@ impl EventHandler for Handler { send_message!( msg.channel_id, &ctx.http, - MENTION_RESPONSES - .choose(&mut rand::thread_rng()) - .expect("We couldn't get any sass") + MENTION_RESPONSES[0] //.choose(&mut rand::random()) + //.expect("We couldn't get any sass") ); } return; @@ -136,16 +135,12 @@ impl EventHandler for Handler { async fn reaction_add(&self, ctx: Context, add_reaction: channel::Reaction) { match add_reaction.message(&ctx.http).await { Ok(message) => match get_message_type(&message) { - MessageType::RoleReactMessage if add_reaction.user_id.unwrap() != CONFIG.bot_id => { - add_role_by_reaction(&ctx, message, add_reaction); - return; + MessageType::RoleReactMessage if add_reaction.user_id == Some(CONFIG.bot_id) => { + add_role_by_reaction(&ctx, message, add_reaction).await } _ if message.author.id != CONFIG.bot_id - || add_reaction.user_id.unwrap() == CONFIG.bot_id => - { - return - } - MessageType::Motion => voting::reaction_add(ctx, add_reaction).await, + || add_reaction.user_id == Some(CONFIG.bot_id) => {} + MessageType::Motion => voting::reaction_add(&ctx, add_reaction).await, MessageType::LogReact => { let react_user = add_reaction.user(&ctx).await.unwrap(); let react_as_string = get_string_from_react(&add_reaction.emoji); @@ -186,28 +181,24 @@ impl EventHandler for Handler { MessageType::RoleReactMessage if removed_reaction.user_id.unwrap() != CONFIG.bot_id => { - remove_role_by_reaction(&ctx, message, removed_reaction); - return; + remove_role_by_reaction(&ctx, message, removed_reaction).await } _ if message.author.id != CONFIG.bot_id - || removed_reaction.user_id.unwrap() == CONFIG.bot_id => - { - return - } - MessageType::Motion => voting::reaction_remove(ctx, removed_reaction).await, + || removed_reaction.user_id.unwrap() == CONFIG.bot_id => {} + MessageType::Motion => voting::reaction_remove(&ctx, removed_reaction).await, _ => {} }, Err(why) => error!("Failed to get react message {:?}", why), } } - fn guild_member_addition( + async fn guild_member_addition( &self, ctx: Context, _guild_id: serenity::model::id::GuildId, the_new_member: Member, ) { - user_management::new_member(&ctx, the_new_member); + user_management::new_member(&ctx, the_new_member).await } // Set a handler to be called on the `ready` event. This is called when a @@ -216,13 +207,13 @@ impl EventHandler for Handler { // private channels, and more. // // In this case, just print what the current user's username is. - fn ready(&self, ctx: Context, ready: Ready) { + async fn ready(&self, ctx: Context, ready: Ready) { info!("{} is connected!", ready.user.name); - sync_all_role_reactions(&ctx); + sync_all_role_reactions(&ctx).await } - fn resume(&self, ctx: Context, _: serenity::model::event::ResumedEvent) { - sync_all_role_reactions(&ctx); + async fn resume(&self, ctx: Context, _: serenity::model::event::ResumedEvent) { + sync_all_role_reactions(&ctx).await } } diff --git a/src/token_management.rs b/src/token_management.rs index badad22ebde7d1c5908fd2e359d22992e046e3e1..5e64cffa56ae17820b928611aa6d9ed5e9105f4f 100644 --- a/src/token_management.rs +++ b/src/token_management.rs @@ -17,7 +17,7 @@ lazy_static! { } fn text_encrypt(plaintext: &str) -> String { - base64::encode(CIPHER.encrypt_vec(plaintext.as_bytes())) + base64::encode(CIPHER.clone().encrypt_vec(plaintext.as_bytes())) } fn text_decrypt(ciphertext: &str) -> Option<String> { @@ -25,7 +25,7 @@ fn text_decrypt(ciphertext: &str) -> Option<String> { warn!("Unable to decode base64 text"); return None }); - guard!(let Ok(decrypted_vec) = CIPHER.decrypt_vec(&cipher_vec) else { + guard!(let Ok(decrypted_vec) = CIPHER.clone().decrypt_vec(&cipher_vec) else { warn!("Text decryption failed"); return None }); diff --git a/src/user_management.rs b/src/user_management.rs index ad3ceb0300b9c11e8a4289fe77755740e331db06..98e91e459db57bfc88aa0f4b72ae74f9d57966df 100644 --- a/src/user_management.rs +++ b/src/user_management.rs @@ -290,7 +290,7 @@ impl Commands { let user = match ctx.http.get_user(member.discord_id as _).await { Ok(u) => u, Err(e) => { - error!("Couldn't find matching Discord ID for username!"); + error!("Couldn't find matching Discord ID for username! {:?}", e); return; } }; @@ -492,8 +492,7 @@ impl Commands { pub async fn clear_info(ctx: Context, msg: Message, field: &str) { if field.trim().is_empty() { // just show the help page from set_info - Commands::set_info(ctx, msg, ""); - return; + return Commands::set_info(ctx, msg, "").await; } match field { "bio" => database::set_member_bio(&msg.author.id.0, None), diff --git a/src/voting.rs b/src/voting.rs index 84574d2134f22c453dc6805cad5d6ec26b5d7d90..ccbf95192b07991846848b6ef66e9f77fefe04df 100644 --- a/src/voting.rs +++ b/src/voting.rs @@ -5,7 +5,7 @@ use serenity::{ }; use std::collections::HashMap; use std::str::FromStr; -use std::sync::Mutex; +use tokio::sync::Mutex; use crate::config::CONFIG; use crate::util::get_string_from_react; @@ -15,8 +15,7 @@ impl Commands { pub async fn move_something(ctx: Context, msg: Message, content: &str) { let motion = content; if !motion.is_empty() { - create_motion(&ctx, &msg, motion); - return; + return create_motion(&ctx, &msg, motion).await; } send_message!( msg.channel_id, @@ -34,8 +33,7 @@ impl Commands { pub async fn poll(ctx: Context, msg: Message, content: &str) { let topic = content; if !topic.is_empty() { - create_poll(&ctx, &msg, topic); - return; + return create_poll(&ctx, &msg, topic).await; } send_message!( msg.channel_id, @@ -167,45 +165,42 @@ lazy_static! { } async fn get_cached_motion(ctx: &Context, msg: &Message) -> MotionInfo { - let mut cached_motions = MOTIONS_CACHE.lock().unwrap(); + let mut cached_motions = MOTIONS_CACHE.lock().await; if !cached_motions.contains_key(&msg.id) { info!("Initialising representation of motion {:?}", msg.id); + let for_votes = msg + .reaction_users( + ctx, + EmojiIdentifier::from_str(&CONFIG.for_vote).unwrap(), + Some(100), + None, + ) + .await + .unwrap(); + let against_votes = msg + .reaction_users( + ctx, + EmojiIdentifier::from_str(&CONFIG.against_vote).unwrap(), + Some(100), + None, + ) + .await + .unwrap(); + let abstain_votes = msg + .reaction_users( + ctx, + EmojiIdentifier::from_str(&CONFIG.abstain_vote).unwrap(), + Some(100), + None, + ) + .await + .unwrap(); let this_motion = MotionInfo { votes: { let mut m = HashMap::new(); - m.insert( - CONFIG.for_vote.to_string(), - msg.reaction_users( - ctx, - EmojiIdentifier::from_str(&CONFIG.for_vote).unwrap(), - Some(100), - None, - ) - .await - .unwrap(), - ); - m.insert( - CONFIG.against_vote.to_string(), - msg.reaction_users( - ctx, - EmojiIdentifier::from_str(&CONFIG.against_vote).unwrap(), - Some(100), - None, - ) - .await - .unwrap(), - ); - m.insert( - CONFIG.abstain_vote.to_string(), - msg.reaction_users( - ctx, - EmojiIdentifier::from_str(&CONFIG.abstain_vote).unwrap(), - Some(100), - None, - ) - .await - .unwrap(), - ); + m.insert(CONFIG.for_vote.to_string(), for_votes); + m.insert(CONFIG.against_vote.to_string(), against_votes); + m.insert(CONFIG.abstain_vote.to_string(), abstain_votes); m }, }; @@ -213,34 +208,15 @@ async fn get_cached_motion(ctx: &Context, msg: &Message) -> MotionInfo { } (*cached_motions.get(&msg.id).unwrap()).clone() } -fn set_cached_motion(id: serenity::model::id::MessageId, motion_info: MotionInfo) { - if let Some(motion) = MOTIONS_CACHE.lock().unwrap().get_mut(&id) { + +async fn set_cached_motion(id: serenity::model::id::MessageId, motion_info: MotionInfo) { + if let Some(motion) = MOTIONS_CACHE.lock().await.get_mut(&id) { *motion = motion_info; return; } warn!("{}", "Couldn't find motion in cache to set"); } -macro_rules! tiebreaker { - ($ctx: expr, $vote: expr, $motion_info: expr) => { - if $motion_info - .votes - .get($vote) - .unwrap() - .iter() - .any(|u| async { - u.has_role($ctx, CONFIG.server_id, CONFIG.tiebreaker_role) - .await - .unwrap() - }) - { - 0.25 - } else { - 0.0 - } - }; -} - async fn update_motion( ctx: &Context, msg: &mut Message, @@ -250,15 +226,33 @@ async fn update_motion( ) { let motion_info: MotionInfo = get_cached_motion(ctx, msg).await; + async fn tiebreaker(ctx: &Context, motion: &MotionInfo, vote_type: &str) -> f32 { + if let Some(votes) = motion.votes.get(vote_type) { + for voter in votes { + match voter + .has_role(ctx, CONFIG.server_id, CONFIG.tiebreaker_role) + .await + { + Ok(true) => return 0.25, + _ => continue, + } + } + 0.0 + } else { + warn!("Couldn't find \"{}\" vote for {:?}", vote_type, motion); + 0.0 + } + } + let for_votes = motion_info.votes.get(&CONFIG.for_vote).unwrap().len() as isize - 1; let against_votes = motion_info.votes.get(&CONFIG.against_vote).unwrap().len() as isize - 1; let abstain_votes = motion_info.votes.get(&CONFIG.abstain_vote).unwrap().len() as isize - 1; - let for_strength = for_votes as f32 + tiebreaker!(ctx, &CONFIG.for_vote, motion_info); + let for_strength = for_votes as f32 + tiebreaker(ctx, &motion_info, &CONFIG.for_vote).await; let against_strength = - against_votes as f32 + tiebreaker!(ctx, &CONFIG.against_vote, motion_info); + against_votes as f32 + tiebreaker(ctx, &motion_info, &CONFIG.against_vote).await; let abstain_strength = - abstain_votes as f32 + tiebreaker!(ctx, &CONFIG.abstain_vote, motion_info); + abstain_votes as f32 + tiebreaker(ctx, &motion_info, &CONFIG.abstain_vote).await; let old_embed = msg.embeds[0].clone(); let topic = old_embed.clone().title.unwrap(); @@ -271,10 +265,13 @@ async fn update_motion( topic ); - let update_status = |e: &mut serenity::builder::CreateEmbed, - status: &str, - last_status_full: String, - topic: &str| async { + fn update_status( + ctx: &Context, + e: &mut serenity::builder::CreateEmbed, + status: &str, + last_status_full: String, + topic: &str, + ) { let last_status = last_status_full.lines().next().expect("No previous status"); if last_status == status { e.field("Status", last_status_full, true); @@ -291,9 +288,15 @@ async fn update_motion( message.push(" is now "); message.push_bold(status); message.push_italic(format!(" (was {})", last_status)); - send_message!(CONFIG.announcement_channel, &ctx.http, message.build()); + let ctx = ctx.clone(); + tokio::spawn(async move { + CONFIG + .announcement_channel + .say(ctx.http, message.build()) + .await + }); } - }; + } if let Err(why) = msg .edit(ctx, |m| { @@ -319,14 +322,14 @@ async fn update_motion( .value; if for_strength > (CONFIG.vote_pool_size as f32 / 2.0) { e.colour(serenity::utils::Colour::TEAL); - update_status(e, "Passed", last_status_full, &topic); + update_status(ctx, e, "Passed", last_status_full, &topic); } else if against_strength + abstain_strength > (CONFIG.vote_pool_size as f32 / 2.0) { e.colour(serenity::utils::Colour::RED); - update_status(e, "Failed", last_status_full, &topic); + update_status(ctx, e, "Failed", last_status_full, &topic); } else { e.colour(serenity::utils::Colour::GOLD); - update_status(e, "Under Consideration", last_status_full, &topic); + update_status(ctx, e, "Under Consideration", last_status_full, &topic); } e.field( format!( @@ -354,7 +357,7 @@ async fn update_motion( } } -pub async fn reaction_add(ctx: Context, add_reaction: channel::Reaction) { +pub async fn reaction_add(ctx: &Context, add_reaction: channel::Reaction) { let react_as_string = get_string_from_react(&add_reaction.emoji); match add_reaction.message(&ctx.http).await { Ok(mut message) => { @@ -407,8 +410,8 @@ pub async fn reaction_add(ctx: Context, add_reaction: channel::Reaction) { vote.retain(|u| u.id != user.id); vote.push(user.clone()); } - set_cached_motion(message.id, motion_info); - update_motion(&ctx, &mut message, &user, "add", add_reaction); + set_cached_motion(message.id, motion_info).await; + update_motion(&ctx, &mut message, &user, "add", add_reaction).await } Ok(false) => { if ![ @@ -434,7 +437,7 @@ pub async fn reaction_add(ctx: Context, add_reaction: channel::Reaction) { } } -pub async fn reaction_remove(ctx: Context, removed_reaction: channel::Reaction) { +pub async fn reaction_remove(ctx: &Context, removed_reaction: channel::Reaction) { match removed_reaction.message(&ctx.http).await { Ok(mut message) => { if let Ok(user) = removed_reaction.user(&ctx).await { @@ -445,8 +448,8 @@ pub async fn reaction_remove(ctx: Context, removed_reaction: channel::Reaction) { vote.retain(|u| u.id != user.id); } - set_cached_motion(message.id, motion_info); - update_motion(&ctx, &mut message, &user, "remove", removed_reaction); + set_cached_motion(message.id, motion_info).await; + update_motion(&ctx, &mut message, &user, "remove", removed_reaction).await; } } Err(why) => {