Commit 5de793cf authored by Timothy du Heaume's avatar Timothy du Heaume
Browse files

sync roles and reactions when connecting or reconnecting

parent 1081456f
......@@ -188,8 +188,13 @@ impl EventHandler for Handler {
// private channels, and more.
// In this case, just print what the current user's username is.
fn ready(&self, _: Context, ready: Ready) {
fn ready(&self, ctx: Context, ready: Ready) {
info!("{} is connected!",;
fn resume(&self, ctx: Context, _: serenity::model::event::ResumedEvent) {
use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
use serenity::{
model::{channel::Message, channel::Reaction},
model::{channel::Message, channel::Reaction, id::UserId},
use crate::util::get_string_from_react;
use crate::util::{get_string_from_react, get_react_from_string};
use crate::config::CONFIG;
pub fn add_role_by_reaction(ctx: Context, msg: Message, added_reaction: Reaction) {
......@@ -22,3 +24,46 @@ pub fn remove_role_by_reaction(ctx: Context, msg: Message, removed_reaction: Rea
return ctx.http.remove_member_role(CONFIG.server_id, *, *role_id.as_u64()).ok();
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|;
// 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(*;
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(*, *rrm.message.as_u64()).ok().map(|m| (m, &rrm.mapping)))
use serenity::model::channel::ReactionType;
use serenity::model::{channel::ReactionType, guild::PartialGuild};
pub fn get_string_from_react(react: ReactionType) -> String {
match react {
......@@ -8,3 +8,9 @@ pub fn get_string_from_react(react: ReactionType) -> String {
_ => format!("Unrecognised reaction type: {:?}", react),
pub fn get_react_from_string(string: String, guild: PartialGuild) -> ReactionType {
guild.emojis.values().find(|e| == string).map_or_else(
|| ReactionType::from(string), // unicode emoji
|custom_emoji| ReactionType::from(
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment