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

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

19 20 21 22 23 24 25
pub fn remove_role_by_reaction(ctx: Context, msg: Message, removed_reaction: Reaction) {
    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);
        return reaction_mapping.mapping.get(&react_as_string);
    }).and_then(|role_id|{
        return ctx.http.remove_member_role(CONFIG.server_id, *msg.author.id.as_u64(), *role_id.as_u64()).ok();
    });
tec's avatar
tec committed
26
}
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69

pub fn add_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
    // 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)
    let all_members = ctx.http.get_guild_members(CONFIG.server_id, Some(1000), None).unwrap();

    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());
            let reactors = message.reaction_users(ctx.http.clone(), reaction_type, Some(255), None).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();
                }
            }
        }
    }
}

fn get_all_role_reaction_message(ctx: &Context) -> Vec<(Message, &'static HashMap<String, serenity::model::id::RoleId>)> {
    let guild = ctx.http.get_guild(CONFIG.server_id).unwrap();
    let channels = ctx.http.get_channels(*guild.id.as_u64()).unwrap();
    return channels.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)))
    }).collect();
}