diff --git a/src/main.rs b/src/main.rs index 071124931ba2692e7cdeef3cf3e7c785858b7dfa..348aa623c77b549c43f0d7a7dddf690179751ceb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -188,11 +188,11 @@ impl EventHandler for Handler { // In this case, just print what the current user's username is. fn ready(&self, ctx: Context, ready: Ready) { info!("{} is connected!", ready.user.name); - reaction_roles::add_all_role_reactions(ctx); + reaction_roles::sync_all_role_reactions(ctx); } fn resume(&self, ctx: Context, _: serenity::model::event::ResumedEvent) { - reaction_roles::add_all_role_reactions(ctx); + reaction_roles::sync_all_role_reactions(ctx); } } diff --git a/src/reaction_roles.rs b/src/reaction_roles.rs index 8add1b40a79672c867c3bf704fb3b54e0a796948..75c0e93457229664d60dc283be3b9e525155a1ee 100644 --- a/src/reaction_roles.rs +++ b/src/reaction_roles.rs @@ -2,7 +2,7 @@ use crate::config::CONFIG; use crate::util::{get_react_from_string, get_string_from_react}; use serenity::{ client::Context, - model::{channel::Message, channel::Reaction, id::UserId}, + model::{channel::Message, channel::Reaction, id::UserId, id::RoleId}, }; use std::collections::{HashMap, HashSet}; use std::iter::FromIterator; @@ -39,7 +39,7 @@ pub fn remove_role_by_reaction(ctx: Context, msg: Message, removed_reaction: Rea }); } -pub fn add_all_role_reactions(ctx: Context) { +pub fn sync_all_role_reactions(ctx: Context) { let messages_with_role_mappings = get_all_role_reaction_message(&ctx); let guild = ctx.http.get_guild(CONFIG.server_id).unwrap(); // this method supports paging, but we probably don't need it since the server only has a couple of @@ -50,6 +50,9 @@ pub fn add_all_role_reactions(ctx: Context) { .get_guild_members(CONFIG.server_id, Some(1000), None) .unwrap(); + let mut roles_to_add: HashMap<UserId, Vec<RoleId>> = HashMap::from_iter(all_members.iter().map(|m| (m.user_id(), Vec::new()))); + let mut roles_to_remove: HashMap<UserId, Vec<RoleId>> = HashMap::from_iter(all_members.iter().map(|m| (m.user_id(), Vec::new()))); + for (message, mapping) in messages_with_role_mappings { for (react, role) in mapping { // the docs say this method can't retrieve more than 100 user reactions at a time, but it seems @@ -61,18 +64,31 @@ pub fn add_all_role_reactions(ctx: Context) { .unwrap(); let reactor_ids: HashSet<UserId> = HashSet::from_iter(reactors.iter().map(|r| r.id)); - // this looks O(n!), but n will probably never be more than three digits, so maybe it's okay? - // one solution might be to batch up all the roles to add/remove for each member and do them - // all at once with .add_roles() - for mut member in all_members.clone() { - if reactor_ids.contains(&member.user_id()) { - member.add_role(ctx.http.clone(), role).unwrap(); - } else { - member.remove_role(ctx.http.clone(), role).unwrap(); + for member in all_members.clone() { + let user_id = &member.user_id(); + if reactor_ids.contains(&user_id) { + if !member.roles.iter().any(|r| r == role) { + roles_to_add.get_mut(&user_id).unwrap().push(*role); + } + } else if member.roles.iter().any(|r| r == role) { + roles_to_remove.get_mut(&user_id).unwrap().push(*role); } } } } + + for (user_id, roles) in roles_to_add { + if !roles.is_empty() { + let mut member = all_members.iter().find(|m| m.user_id() == user_id).unwrap().clone(); + member.add_roles(ctx.http.clone(), &roles[..]).unwrap(); + } + } + for (user_id, roles) in roles_to_remove { + if !roles.is_empty() { + let mut member = all_members.iter().find(|m| m.user_id() == user_id).unwrap().clone(); + member.remove_roles(ctx.http.clone(), &roles[..]).unwrap(); + } + } } fn get_all_role_reaction_message(