reaction_roles.rs 4.75 KB
Newer Older
tec's avatar
tec committed
1 2
use crate::config::CONFIG;
use crate::util::{get_react_from_string, get_string_from_react};
3
use serenity::{
tec's avatar
tec committed
4
    client::Context,
5
    model::{channel::Message, channel::Reaction, id::UserId, id::RoleId},
6
};
tec's avatar
tec committed
7 8
use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
tec's avatar
tec committed
9

10
pub fn add_role_by_reaction(ctx: Context, msg: Message, added_reaction: Reaction) {
tec's avatar
tec committed
11 12 13 14 15 16
    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.emoji);
Timothy du Heaume's avatar
Timothy du Heaume committed
17
            reaction_mapping.mapping.get(&react_as_string)
tec's avatar
tec committed
18 19
        })
        .and_then(|role_id| {
Timothy du Heaume's avatar
Timothy du Heaume committed
20
            ctx.http
tec's avatar
tec committed
21
                .add_member_role(CONFIG.server_id, *msg.author.id.as_u64(), *role_id.as_u64())
Timothy du Heaume's avatar
Timothy du Heaume committed
22
                .ok()
tec's avatar
tec committed
23
        });
tec's avatar
tec committed
24 25
}

26
pub fn remove_role_by_reaction(ctx: Context, msg: Message, removed_reaction: Reaction) {
tec's avatar
tec committed
27 28 29 30 31 32
    CONFIG
        .react_role_messages
        .iter()
        .find(|rrm| rrm.message == msg.id)
        .and_then(|reaction_mapping| {
            let react_as_string = get_string_from_react(removed_reaction.emoji);
Timothy du Heaume's avatar
Timothy du Heaume committed
33
            reaction_mapping.mapping.get(&react_as_string)
tec's avatar
tec committed
34 35
        })
        .and_then(|role_id| {
Timothy du Heaume's avatar
Timothy du Heaume committed
36
            ctx.http
tec's avatar
tec committed
37
                .remove_member_role(CONFIG.server_id, *msg.author.id.as_u64(), *role_id.as_u64())
Timothy du Heaume's avatar
Timothy du Heaume committed
38
                .ok()
tec's avatar
tec committed
39
        });
tec's avatar
tec committed
40
}
41

42
pub fn sync_all_role_reactions(ctx: Context) {
43 44 45 46 47
    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
    // hundred members. the Reaction.users() method can apparently only retrieve 100 users at once, but
    // this one seems to work fine when set to 1000 (I tried 10,000 but the api returned a 400)
tec's avatar
tec committed
48 49 50 51
    let all_members = ctx
        .http
        .get_guild_members(CONFIG.server_id, Some(1000), None)
        .unwrap();
52

53 54 55
    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())));

56 57 58 59 60 61
    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
            // to work fine when set to 255...
            // 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());
tec's avatar
tec committed
62 63 64
            let reactors = message
                .reaction_users(ctx.http.clone(), reaction_type, Some(255), None)
                .unwrap();
65 66
            let reactor_ids: HashSet<UserId> = HashSet::from_iter(reactors.iter().map(|r| r.id));

67 68 69 70 71 72 73 74
            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);
75 76 77 78
                }
            }
        }
    }
79 80 81 82 83 84 85 86 87 88 89 90 91

    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();
        }
    }
92 93
}

tec's avatar
tec committed
94 95 96 97 98 99
fn get_all_role_reaction_message(
    ctx: &Context,
) -> Vec<(
    Message,
    &'static HashMap<String, serenity::model::id::RoleId>,
)> {
100 101
    let guild = ctx.http.get_guild(CONFIG.server_id).unwrap();
    let channels = ctx.http.get_channels(*guild.id.as_u64()).unwrap();
Timothy du Heaume's avatar
Timothy du Heaume committed
102
    channels
tec's avatar
tec committed
103 104 105 106 107 108 109 110 111 112 113 114
        .iter()
        .flat_map(|channel| {
            let ctxx = ctx.clone();
            // 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()
            CONFIG.react_role_messages.iter().filter_map(move |rrm| {
                ctxx.http
                    .get_message(*channel.id.as_u64(), *rrm.message.as_u64())
                    .ok()
                    .map(|m| (m, &rrm.mapping))
            })
        })
Timothy du Heaume's avatar
Timothy du Heaume committed
115
        .collect()
116
}