diff --git a/TODO.org b/TODO.org index cd92806b3b86295f7af86d28e6d8ade420940a5f..dc26fd1bc0d1762c5c2fac99e75c909ce0b481b1 100644 --- a/TODO.org +++ b/TODO.org @@ -7,10 +7,10 @@ _Involves: File r/w + parsing, discord reactions_ So, for reaction roles, afaict this is what needs to be done - [X] Migrate config.rs to something like ~config.yml~ - - [-] Complete ~reaction_roles.rs~ + - [X] Complete ~reaction_roles.rs~ - [X] Load from config (roles, and the rr msg, if they exist) - [X] Monitor reactions, update user roles etc. - - [ ] On updated to ~config.yml~ (and on bot load, now that I think of it) overwrite the rr msg with content based on roles in config + - [X] Only allow reacts which correspond to roles * LDAP Integration diff --git a/src/config.test.yml b/src/config.test.yml index 6f8a814c7ca5d6b600aab7368e47601cbb7aa04f..b9bf21de834b9a958a4124bb2dd4c9dfa90d99a6 100644 --- a/src/config.test.yml +++ b/src/config.test.yml @@ -20,8 +20,10 @@ disapprove_react: "⬇" unsure_react: "â”" react_role_messages: - - message: 673400351277187072 + - message: 674164298632790026 mapping: ðŸŠ: 609708723006472198 # Autonomous Alligators ðŸƒ: 609708839243087892 # Bipedal Bison 🦆: 609708889763479562 # Omnipresent Ostriches + j5: 607478818038480937 # Vote role + 👻: 634415546804338688 # Background charachter diff --git a/src/main.rs b/src/main.rs index 348aa623c77b549c43f0d7a7dddf690179751ceb..299cfe2cf28ba1bae216f024189d05a6cf4d3fb3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -107,7 +107,9 @@ impl EventHandler for Handler { match add_reaction.message(&ctx.http) { Ok(message) => { let message_type = get_message_type(&message); - if message_type == MessageType::RoleReactMessage { + if message_type == MessageType::RoleReactMessage + && add_reaction.user_id.0 != CONFIG.bot_id + { add_role_by_reaction(ctx, message, add_reaction); return; } @@ -120,7 +122,7 @@ impl EventHandler for Handler { } MessageType::LogReact => { let react_user = add_reaction.user(&ctx).unwrap(); - let react_as_string = get_string_from_react(add_reaction.emoji.clone()); + let react_as_string = get_string_from_react(&add_reaction.emoji); if Utc::now().timestamp() - message.timestamp.timestamp() > 300 { warn!( "The logreact message {} just tried to use is too old", @@ -129,8 +131,8 @@ impl EventHandler for Handler { return; } info!( - "The react {} just added is {:?}", - react_user.name, react_as_string + "The react {} just added is {:?}. In full: {:?}", + react_user.name, react_as_string, add_reaction.emoji ); let mut msg = MessageBuilder::new(); msg.push_italic(react_user.name); @@ -155,7 +157,9 @@ impl EventHandler for Handler { match removed_reaction.message(&ctx.http) { Ok(message) => { let message_type = get_message_type(&message); - if message_type == MessageType::RoleReactMessage { + if message_type == MessageType::RoleReactMessage + && removed_reaction.user_id != CONFIG.bot_id + { remove_role_by_reaction(ctx, message, removed_reaction); return; } diff --git a/src/reaction_roles.rs b/src/reaction_roles.rs index 6affc31e7c4d2604d53b7aef295e21a86c85c139..35861b9884ce757f059392758e82b17a6684221a 100644 --- a/src/reaction_roles.rs +++ b/src/reaction_roles.rs @@ -7,33 +7,48 @@ use serenity::{ use std::collections::{HashMap, HashSet}; use std::iter::FromIterator; +macro_rules! e { + ($error: literal, $x:expr) => { + match $x { + Ok(_) => (), + Err(why) => error!($error, why), + } + }; +} + pub fn add_role_by_reaction(ctx: Context, msg: Message, added_reaction: Reaction) { - CONFIG + let user = added_reaction + .user_id + .to_user(&ctx) + .expect("Unable to get user"); + if let Some(role_id) = CONFIG .react_role_messages .iter() .find(|rrm| rrm.message == msg.id) .and_then(|reaction_mapping| { - let react_as_string = get_string_from_react(added_reaction.clone().emoji); + let react_as_string = get_string_from_react(&added_reaction.emoji); reaction_mapping.mapping.get(&react_as_string) }) - .and_then(|role_id| { - info!( - "{} requested role {}", - added_reaction - .user_id - .to_user(&ctx) - .expect("Unable to get user") - .name, - role_id - ); - ctx.http - .add_member_role( - CONFIG.server_id, - added_reaction.user_id.0, - *role_id.as_u64(), - ) - .ok() - }); + { + info!( + "{} requested role '{}'", + user.name, + role_id + .to_role_cached(&ctx) + .expect("Unable to get role") + .name + ); + ctx.http + .add_member_role( + CONFIG.server_id, + added_reaction.user_id.0, + *role_id.as_u64(), + ) + .ok(); + } else { + warn!("{} provided invalid react for role", user.name); + e!("Unable to delete react: {:?}", added_reaction.delete(&ctx)); + } } pub fn remove_role_by_reaction(ctx: Context, msg: Message, removed_reaction: Reaction) { @@ -42,11 +57,18 @@ pub fn remove_role_by_reaction(ctx: Context, msg: Message, removed_reaction: Rea .iter() .find(|rrm| rrm.message == msg.id) .and_then(|reaction_mapping| { - let react_as_string = get_string_from_react(removed_reaction.clone().emoji); + let react_as_string = get_string_from_react(&removed_reaction.emoji); reaction_mapping.mapping.get(&react_as_string) }) .and_then(|role_id| { - info!("{} requested removal of role {}", msg.author.name, role_id); + info!( + "{} requested removal of role '{}'", + msg.author.name, + role_id + .to_role_cached(&ctx) + .expect("Unable to get role") + .name + ); ctx.http .remove_member_role( CONFIG.server_id, @@ -81,16 +103,38 @@ pub fn sync_all_role_reactions(ctx: Context) { for (message, mapping) in messages_with_role_mappings { i += 1; info!(" Sync: prossessing message #{}", i); + for react in &message.reactions { + let react_as_string = get_string_from_react(&react.reaction_type); + if mapping.contains_key(&react_as_string) { + continue; + } + info!( + " message #{}: Removing non-role react '{}'", + i, react_as_string + ); + for _illegal_react in + &message.reaction_users(&ctx, react.reaction_type.clone(), Some(100), None) + { + warn!(" need to implement react removal"); + } + } for (react, role) in mapping { - // the docs say this method can't retrieve more than 100 user reactions at a time, but it seems - // to work fine when set to 255... + info!(" message #{}: processing react '{}'", i, react); // TODO: proper pagination for the unlikely scenario that there are more than 100 (255?) reactions? let reaction_type = get_react_from_string(react.clone(), guild.clone()); let reactors = message - .reaction_users(ctx.http.clone(), reaction_type, Some(255), None) + .reaction_users(ctx.http.clone(), reaction_type.clone(), Some(100), None) .unwrap(); let reactor_ids: HashSet<UserId> = HashSet::from_iter(reactors.iter().map(|r| r.id)); + // ensure bot has reacted + if !reactor_ids.contains(&UserId::from(CONFIG.bot_id)) { + e!( + "Unable to add reaction, {:?}", + message.react(&ctx, reaction_type) + ); + } + for member in all_members.clone() { let user_id = &member.user_id(); if reactor_ids.contains(&user_id) { diff --git a/src/util.rs b/src/util.rs index b168e8ac559409787aebf668a56bb199c61b409a..75aba7ffb49697e3a9d8ad3778fc586bd1816785 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,12 +1,12 @@ use serenity::model::{channel::ReactionType, guild::PartialGuild}; -pub fn get_string_from_react(react: ReactionType) -> String { +pub fn get_string_from_react(react: &ReactionType) -> String { match react { ReactionType::Custom { name: Some(name), .. - } => name, + } => name.to_string(), ReactionType::Custom { id, name: None, .. } => id.to_string(), - ReactionType::Unicode(name) => name, + ReactionType::Unicode(name) => name.to_string(), _ => format!("Unrecognised reaction type: {:?}", react), } } @@ -18,6 +18,6 @@ pub fn get_react_from_string(string: String, guild: PartialGuild) -> ReactionTyp .find(|e| e.name == string) .map_or_else( || ReactionType::from(string), // unicode emoji - |custom_emoji| ReactionType::from(custom_emoji.id), + |custom_emoji| ReactionType::from(custom_emoji.clone()), ) } diff --git a/src/voting.rs b/src/voting.rs index 90b7c6e888b5b13d7021f2072ca77c6e060e0d92..cc26869369323a495aad3f57b461ec2d013a664e 100644 --- a/src/voting.rs +++ b/src/voting.rs @@ -179,17 +179,17 @@ fn get_cached_motion(ctx: &Context, msg: &Message) -> MotionInfo { let mut m = HashMap::new(); m.insert( CONFIG.for_vote.to_string(), - msg.reaction_users(ctx, CONFIG.for_vote.to_string(), None, None) + msg.reaction_users(ctx, CONFIG.for_vote.to_string(), Some(100), None) .unwrap(), ); m.insert( CONFIG.against_vote.to_string(), - msg.reaction_users(ctx, CONFIG.against_vote.to_string(), None, None) + msg.reaction_users(ctx, CONFIG.against_vote.to_string(), Some(100), None) .unwrap(), ); m.insert( CONFIG.abstain_vote.to_string(), - msg.reaction_users(ctx, CONFIG.abstain_vote.to_string(), None, None) + msg.reaction_users(ctx, CONFIG.abstain_vote.to_string(), Some(100), None) .unwrap(), ); m @@ -253,7 +253,7 @@ fn update_motion( " {:10} {:6} {} on {}", user.name, change, - get_string_from_react(reaction.emoji), + get_string_from_react(&reaction.emoji), topic ); @@ -339,7 +339,7 @@ fn update_motion( } pub fn reaction_add(ctx: Context, add_reaction: channel::Reaction) { - let react_as_string = get_string_from_react(add_reaction.emoji.clone()); + let react_as_string = get_string_from_react(&add_reaction.emoji); match add_reaction.message(&ctx.http) { Ok(mut message) => { if let Ok(user) = add_reaction.user(&ctx) { @@ -414,7 +414,7 @@ pub fn reaction_remove(ctx: Context, removed_reaction: channel::Reaction) { let mut motion_info = get_cached_motion(&ctx, &message); if let Some(vote) = motion_info .votes - .get_mut(&get_string_from_react(removed_reaction.emoji.clone())) + .get_mut(&get_string_from_react(&removed_reaction.emoji)) { vote.retain(|u| u.id != user.id); }