Esempio n. 1
0
        public async Task ClearRender(CommandContext ctx, [RemainingText] string accountName)
        {
            await ctx.ReplyWorkingAsync();

            //Make sure account name is valid
            if (string.IsNullOrEmpty(accountName))
            {
                await ctx.ReplyReactionAsync(false);

                return;
            }

            //Get all the keys. Note this is custom because the Profile doesnt actually contain this information
            var cacheKey  = Namespace.Combine(Bot.SiegeManager.RedisPrefix, "profiles", accountName.ToLowerInvariant(), "render");
            var searchKey = cacheKey + "*";

            var redis  = Redis as StackExchangeClient;
            var server = redis.GetServersEnumable().First();
            var keys   = server.Keys(pattern: searchKey);

            //Prepare a transaction
            var t = Redis.CreateTransaction();

            //Delete all the keys
            foreach (var k in keys)
            {
                _ = t.RemoveAsync(k);
            }

            //Execute Transactoin
            await t.ExecuteAsync();

            await ctx.ReplyReactionAsync(true);
        }
Esempio n. 2
0
        /// <summary>Removes a tag</summary>
        public async Task RemoveTagAsync(DiscordGuild guild, Tag tag)
        {
            var transaction = Redis.CreateTransaction();

            _ = transaction.RemoveHashSetAsync(Namespace.Combine(guild.Id, "tags"), tag.Name);              //Remove from guild list
            _ = transaction.RemoveAsync(Namespace.Combine(guild.Id, "tags", tag.Name));                     //Remove from owner list
            _ = transaction.RemoveHashSetAsync(Namespace.Combine(guild.Id, tag.Owner, "tags"), tag.Name);   //Remove tag
            await transaction.ExecuteAsync();
        }
Esempio n. 3
0
        /// <summary>Transfers a tag a tag</summary>
        public async Task TransferTagAsync(DiscordGuild guild, DiscordUser newOwner, Tag tag)
        {
            var transaction = Redis.CreateTransaction();

            _ = transaction.AddHashSetAsync(Namespace.Combine(guild.Id, newOwner.Id, "tags"), tag.Name);                                //Add to new user list
            _ = transaction.RemoveHashSetAsync(Namespace.Combine(guild.Id, tag.Owner, "tags"), tag.Name);                               //Remove from old user list
            _ = transaction.StoreStringAsync(Namespace.Combine(guild.Id, "tags", tag.Name), Tag.KEY_OWNER, newOwner.Id.ToString());     //Edit owner
            await transaction.ExecuteAsync();
        }
Esempio n. 4
0
        /// <summary>
        /// Fetches a profile from the DiscordUser. Returns null if there is none linked
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        public async Task <SiegeProfile> GetProfileAsync(DiscordUser user, bool recache = false)
        {
            string accountName = await Redis.FetchStringAsync(Namespace.Combine(RedisPrefix, "links"), user.Id.ToString(), null);

            if (accountName == null)
            {
                return(null);
            }

            return(await GetProfileAsync(accountName, recache));
        }
Esempio n. 5
0
        /// <summary>Fetches a tag</summary>
        public async Task <Tag> GetTagAsync(DiscordGuild guild, string name)
        {
            name = name.ToLowerInvariant();     //First thing is we will lower the name. All names will be case insensitive.
            var tag = await Redis.FetchObjectAsync <Tag>(Namespace.Combine(guild.Id, "tags", name));

            if (tag != null)
            {
                tag.Name = name;
            }
            return(tag);
        }
Esempio n. 6
0
        /// <summary>
        /// Gets a role that has been linked to a emoji
        /// </summary>
        /// <param name="guild"></param>
        /// <param name="emoji"></param>
        /// <returns></returns>
        public async Task <DiscordRole> GetReactionRoleAsync(DiscordMessage message, DiscordEmoji emoji)
        {
            string key = Namespace.Combine(message.Channel.Guild, REDIS_REACT_ROLE, message);
            string val = await Redis.FetchStringAsync(key, emoji.Id.ToString(), null);

            if (string.IsNullOrEmpty(val))
            {
                return(null);
            }
            return(message.Channel.Guild.GetRole(ulong.Parse(val)));
        }
Esempio n. 7
0
        /// <summary>
        /// Gets the guild settings
        /// </summary>
        /// <param name="guild"></param>
        /// <returns></returns>
        public static async Task <GuildSettings> GetGuildSettingsAsync(DiscordGuild guild)
        {
            var settings = await Koala.Bot.Redis.FetchObjectAsync <GuildSettings>(Namespace.Combine(guild, "settings"));

            if (settings == null)
            {
                settings = new GuildSettings(guild, DefaultPrefix);
                await settings.SaveAsync();
            }

            settings.Guild = guild;
            return(settings);
        }
Esempio n. 8
0
        /// <summary>
        /// Stores a reply reaction
        /// </summary>
        /// <param name="ctx"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        public async Task StoreReactionAsync(CommandContext ctx, DiscordEmoji reaction)
        {
            //Store the redis object
            string key = Namespace.Combine(ctx.Guild, "replies", ctx.Message);
            await Redis.StoreObjectAsync(key, new Reply()
            {
                CommandMsg    = ctx.Message.Id,
                ResponseEmote = reaction.GetDiscordName(),
                ResponseType  = Reply.SnowflakeType.Reaction
            });

            await Redis.SetExpiryAsync(key, ReplyTimeout);
        }
Esempio n. 9
0
        public async Task <Optional <World> > ConvertAsync(string value, CommandContext ctx)
        {
            //Check for alias
            var alias = await Redis.FetchStringAsync(Namespace.Combine(ctx.Guild, "starwatch", "world-alias", value));

            alias = alias ?? value;

            //Parse the world
            var world = World.Parse(alias);

            if (world != null)
            {
                return(Optional.FromValue(world));
            }
            return(Optional.FromNoValue <World>());
        }
Esempio n. 10
0
        /// <summary>
        /// Updates the operators
        /// </summary>
        /// <param name="recache"></param>
        /// <returns></returns>
        public async Task <Dictionary <string, Operator> > UpdateOperatorsAsync(bool recache = false)
        {
            var cacheName = Namespace.Combine(Manager.RedisPrefix, "profiles", ApiName, "operators");

            if (!recache)
            {
                //Fetch the cached base64 hashset
                var d64 = await Manager.Redis.FetchHashMapAsync(cacheName);

                if (d64.Count != 0)
                {
                    //Decode the base64 to operators
                    var dop = new Dictionary <string, Operator>(d64.Count);
                    foreach (var kp in d64)
                    {
                        dop.Add(kp.Key, Operator.FromBase64(kp.Value));
                    }

                    //Return
                    return(dop);
                }
            }

            //Fetch the operators
            var operators = await Manager.InternalGetStringFromAPIAsync <Dictionary <string, Operator> >($"/operators.php?username={ApiName}");

            //Convert ot base64 and save
            var op64 = new Dictionary <string, string>(operators.Count);

            foreach (var kp in operators)
            {
                if (kp.Value != null)
                {
                    op64.Add(kp.Key, kp.Value.ToBase64());
                }
            }

            await Manager.Redis.StoreHashMapAsync(cacheName, op64);

            await Manager.Redis.SetExpiryAsync(cacheName, OperatorTTL);

            //Return the ops
            return(operators);
        }
Esempio n. 11
0
        /// <summary>Creates a tag</summary>
        public async Task <Tag> CreateTagAsync(DiscordGuild guild, DiscordUser owner, string name, string content)
        {
            Tag tag = new Tag()
            {
                Creator     = owner.Id,
                Owner       = owner.Id,
                Content     = content,
                DateCreated = DateTime.UtcNow
            };

            var transaction = Redis.CreateTransaction();

            _ = transaction.StoreObjectAsync(Namespace.Combine(guild.Id, "tags", name), tag);       //Store tag
            _ = transaction.AddHashSetAsync(Namespace.Combine(guild.Id, "tags"), name);             //Add to guild list
            _ = transaction.AddHashSetAsync(Namespace.Combine(guild.Id, owner.Id, "tags"), name);   //Add to owner list
            await transaction.ExecuteAsync();

            return(tag);
        }
Esempio n. 12
0
        /// <summary>
        /// Removes the black bacon role to a user.
        /// </summary>
        /// <param name="member"></param>
        /// <param name="moderator"></param>
        /// <param name="reason"></param>
        /// <returns></returns>
        public async Task <bool> RemoveBlackBaconAsync(DiscordMember member, DiscordMember moderator = null)
        {
            //Get the guild settings, and make sure they are valid
            var settings = await GuildSettings.GetGuildSettingsAsync(member.Guild);

            if (settings.GuildId < 0)
            {
                throw new Exception("Black Bacon role not yet defined.");
            }

            var bbrole = settings.GetBlackBaconRole();

            if (bbrole == null)
            {
                throw new Exception("Black Bacon role not yet defined or has since been deleted.");
            }

            //Get all the roles and clear out the store
            var rStoreKey = Namespace.Combine(member.Guild, member, "bbroles");
            var rStore    = await Bot.Redis.FetchHashSetAsync(rStoreKey);

            await Bot.Redis.RemoveAsync(rStoreKey);

            if (rStore == null)
            {
                return(false);
            }

            //Prepare the roles
            var roles = rStore
                        .Select(str => ulong.TryParse(str, out var id) ? id : 0)
                        .Select(id => member.Guild.GetRole(id))
                        .Where(role => role != null);

            //Award the roles
            await member.ReplaceRolesAsync(roles, member.Username + " removed BB");

            await Bot.ModerationManager.RecordModerationAsync("blackbacon", moderator, member, "Black Bacon Removed");

            return(true);
        }
Esempio n. 13
0
        /// <summary>
        /// Applies the black bacon role to a user.
        /// </summary>
        /// <param name="member"></param>
        /// <param name="moderator"></param>
        /// <param name="reason"></param>
        /// <returns></returns>
        public async Task GiveBlackBaconAsync(DiscordMember member, DiscordMember moderator = null, string reason = null)
        {
            //Get the guild settings, and make sure they are valid
            var settings = await GuildSettings.GetGuildSettingsAsync(member.Guild);

            if (settings.GuildId < 0)
            {
                throw new Exception("Black Bacon role not yet defined.");
            }

            var bbrole = settings.GetBlackBaconRole();

            if (bbrole == null)
            {
                throw new Exception("Black Bacon role not yet defined or has since been deleted.");
            }

            //Get the keys
            var rStoreKey = Namespace.Combine(member.Guild, member, "bbroles");

            if (await Redis.ExistsAsync(rStoreKey))
            {
                throw new Exception("The user has already been black baconed.");
            }

            //Calculate what roles we should add to the list
            var rStore = member.Roles
                         .Prepend(member.Guild.EveryoneRole)
                         .Where(r => r.Id != bbrole.Id)
                         .Select(r => r.Id.ToString())
                         .ToHashSet();

            //Add the hash, apply the roles and update their sync
            await Bot.Redis.AddHashSetAsync(rStoreKey, rStore);

            await member.ReplaceRolesAsync(new DiscordRole[] { bbrole });

            await Bot.ModerationManager.RecordModerationAsync("blackbacon", moderator, member, reason);
        }
Esempio n. 14
0
        /// <summary>Tries to enforce the roles of a user. Returns true if the user is being BB.</summary>
        public async Task <bool> TryEnforceBlackBacon(DiscordMember member)
        {
            //Make sure the user is actually muted
            if (!await Redis.ExistsAsync(Namespace.Combine(member.Guild, member, "bbroles")))
            {
                return(false);
            }

            //Get the settings
            var settings = await member.Guild.GetSettingsAsync();

            var bbrole = settings.GetBlackBaconRole();

            if (bbrole == null)
            {
                return(false);
            }

            //Remute the player
            if (member.Roles.Count() != 1 || !member.Roles.Contains(bbrole))
            {
                try
                {
                    Logger.Log("Enforcing Black Bacon");
                    await member.ReplaceRolesAsync(new DiscordRole[] { bbrole });
                }
                catch (DSharpPlus.Exceptions.RateLimitException)
                {
                    //The have triggered the ratelimit, probably abusing something. Kick em for saftey.
                    Logger.Log("Kicking {0} because hit ratelimit while trying to enforce it.", member);
                    await KickMemberAsync(member, reason : "Black Bacon Enforcement hit ratelimit while trying to enforce it.");
                }
                return(true);
            }

            return(true);
        }
Esempio n. 15
0
        /// <summary>
        /// Sets the enforcement of a user nickname
        /// </summary>
        /// <param name="member">The member to enforce</param>
        /// <param name="nickname">The nickname to enforce</param>
        /// <returns></returns>
        public async Task <bool> SetNicknameEnforcementAsync(DiscordMember member, string nickname, TimeSpan?duration = null, DiscordMember moderator = null)
        {
            if (member == null)
            {
                throw new ArgumentNullException("member");
            }


            string nicknameKey = Namespace.Combine(member.Guild, member, "nickname");

            if (string.IsNullOrWhiteSpace(nickname))
            {
                //Remove the enforcement
                bool success = await Redis.RemoveAsync(nicknameKey);

                if (success)
                {
                    await member.ModifyAsync((m) => { m.Nickname = null; m.AuditLogReason = "Enforcement Removed"; });

                    await Bot.ModerationManager.RecordModerationAsync("nickname", moderator, member, "Enforcement Removed");
                }

                return(success);
            }
            else
            {
                //Add the enforcement and then trigger it
                await Redis.StoreStringAsync(nicknameKey, nickname, duration);

                await member.ModifyAsync((m) => { m.Nickname = nickname; m.AuditLogReason = "Enforcement Created"; });

                await Bot.ModerationManager.RecordModerationAsync("nickname", moderator, member, "Enforced to " + nickname);

                return(true);
            }
        }
Esempio n. 16
0
        /// <summary>Tries to enforce the nickname of a user</summary>
        public async Task <bool> TryEnforceNickname(DiscordMember member)
        {
            var enforcedNickname = await Redis.FetchStringAsync(Namespace.Combine(member.Guild, member, "nickname"), @default : null);

            if (enforcedNickname != null && !member.Nickname.Equals(enforcedNickname, StringComparison.InvariantCultureIgnoreCase))
            {
                try
                {
                    //Enforce the nickname
                    Logger.Log("Enforcing {0} Nickname.", member);
                    await member.ModifyAsync(model => { model.Nickname = enforcedNickname; model.AuditLogReason = "Nickname Enforcemnet"; });
                }
                catch (DSharpPlus.Exceptions.RateLimitException)
                {
                    //The have triggered the ratelimit, probably abusing something. Kick em for saftey.
                    Logger.Log("Kicking {0} because hit ratelimit while trying to enforce it.", member);
                    await KickMemberAsync(member, reason : "Nickname Enforcement hit ratelimit while trying to enforce it.");
                }

                return(true);
            }

            return(false);
        }
Esempio n. 17
0
        /// <summary>Fetches a profile</summary>
        public async Task <ProfileData> UpdateProfileAsync(bool recache = false)
        {
            var cacheName = Namespace.Combine(Manager.RedisPrefix, "profiles", ApiName, "b64");

            if (!recache)
            {
                //Fetch the base64 encoded profile. If we find it, then store it.
                var p64 = await Manager.Redis.FetchStringAsync(cacheName);

                if (!string.IsNullOrWhiteSpace(p64))
                {
                    return(Profile = ProfileData.FromBase64(p64));
                }
            }

            //Fetch the client
            Profile = await Manager.InternalGetStringFromAPIAsync <ProfileData>($"/profile.php?username={ApiName}");

            await Manager.Redis.StoreStringAsync(cacheName, Profile.ToBase64());

            await Manager.Redis.SetExpiryAsync(cacheName, ProfileDataTLL);

            return(Profile);
        }
Esempio n. 18
0
        /// <summary>
        /// Gets a rendering of the profile banner.
        /// </summary>
        /// <param name="recache"></param>
        /// <returns></returns>
        public async Task <byte[]> GetProfileRenderAsync(bool recache = false)
        {
            if (Profile == null)
            {
                throw new InvalidOperationException("Cannot render a profile if there is no profile");
            }

            if (MMRHistory == null)
            {
                throw new InvalidOperationException("Cannot render a profile if there is no MMR data");
            }

            //Prepare a code which we will use for the cache
            var cacheCode = RENDER_CACHE_REVISION ^ Profile.GetCacheCode() ^ MMRHistory.GetCacheCode();
            var cacheName = Namespace.Combine(Manager.RedisPrefix, "profiles", ApiName, "render", cacheCode);

            //Fetch the cache
            if (!recache && !NO_CACHE_RENDER)
            {
                var cacheResult = await Manager.Redis.FetchStringAsync(cacheName);

                if (cacheResult != null)
                {
                    return(Convert.FromBase64String(cacheResult));
                }
            }

            //Regenerate and recache
            var renderResult = await RenderProfileAsync();

            await Manager.Redis.StoreStringAsync(cacheName, Convert.ToBase64String(renderResult));

            await Manager.Redis.SetExpiryAsync(cacheName, ProfileRenderTLL);

            return(renderResult);
        }
Esempio n. 19
0
            public async Task SetAlias(CommandContext ctx, string alias, World world = null)
            {
                //World is null, so delete the alias
                if (world == null)
                {
                    var success = await Redis.RemoveAsync(Namespace.Combine(ctx.Guild, "starwatch", "world-alias", alias));

                    if (success)
                    {
                        await ctx.ReplyAsync("World Removed");
                    }
                    else
                    {
                        await ctx.ReplyReactionAsync(false);
                    }
                }
                else
                {
                    //Sets the alias. Note that this is used in the WorldConverter.
                    await Redis.StoreStringAsync(Namespace.Combine(ctx.Guild, "starwatch", "world-alias", alias), world.Whereami);

                    await ctx.ReplyReactionAsync(true);
                }
            }
Esempio n. 20
0
        /// <summary>
        /// Syncs the recent changes to the server.
        /// </summary>
        /// <returns></returns>
        public async Task SyncChanges()
        {
            //Sync the semaphore
            await _semaphore.WaitAsync();

            try
            {
                //Just skip if there is no maps
                if (_userTallies.Count == 0)
                {
                    return;
                }

                //Prepare the transaction and put everything in
                HashSet <ulong> changedUsers  = new HashSet <ulong>(_userTallies.Count);
                HashSet <ulong> changedGuilds = new HashSet <ulong>(Bot.Discord.Guilds.Count);
                long            totalMessages = 0;

                var transaction = Redis.CreateTransaction();
                foreach (var userTally in _userTallies)
                {
                    ulong userId = userTally.Key;
                    changedUsers.Add(userId);

                    foreach (var guildTracking in userTally.Value)
                    {
                        changedGuilds.Add(guildTracking.Key);
                        totalMessages += guildTracking.Value.tally;
                        _              = transaction.IncrementAsync(Namespace.Combine(guildTracking.Key, userId, "posts"), value: guildTracking.Value.tally);
                        _              = transaction.IncrementAsync(Namespace.Combine("global", userId, "posts"), value: guildTracking.Value.tally);

                        if (guildTracking.Value.time.HasValue)
                        {
                            _ = transaction.StoreStringAsync(Namespace.Combine(guildTracking.Key, userId, "activity"), guildTracking.Value.time.Value.ToOADate().ToString());
                        }
                    }
                }

                //Add the total tally
                _ = transaction.IncrementAsync(Namespace.Combine("global", "posts"), totalMessages);

                //Execute the transaction
                await transaction.ExecuteAsync();

                if (ChangesSynced != null)
                {
                    await ChangesSynced?.Invoke(new ChangesSyncedEventArgs(changedUsers, changedGuilds));
                }

                //Clear the tallies
                _userTallies.Clear();
            }
            catch (Exception e)
            {
                await Bot.LogException(e);
            }
            finally
            {
                //We are finally done so release it.
                _semaphore.Release();
            }
        }
Esempio n. 21
0
        /// <summary>
        /// Gets the global counts of messages
        /// </summary>
        /// <returns></returns>
        public async Task <long> GetGlobalCountAsync()
        {
            string tallyString = await Redis.FetchStringAsync(Namespace.Combine("global", "posts"), "0");

            return(long.Parse(tallyString));
        }
Esempio n. 22
0
        /// <summary>
        /// Updates the MMR History.
        /// </summary>
        /// <param name="update"></param>
        /// <returns></returns>
        public async Task <MMRHistory> UpdateMMRHistoryAsync()
        {
            //We need to first update the profile
            if (Profile == null)
            {
                throw new InvalidOperationException("Cannot get MMR if there is no profile");
            }

            //Fetch the history
            var cacheName   = Namespace.Combine(Manager.RedisPrefix, "profiles", ApiName, "history");
            var prevHistory = await Manager.Redis.FetchObjectAsync <MMRHistory>(cacheName);

            if (prevHistory == null || prevHistory.Season != Manager.Season)
            {
                if (Profile != null)
                {
                    //Profile exists, so lets setup the default
                    prevHistory = new MMRHistory()
                    {
                        Season  = Manager.Season,
                        Minimum = Profile.MetaData.MMR,
                        Current = Profile.MetaData.MMR,
                        Maximum = Profile.MetaData.BestMMR,
                        Average = Profile.MetaData.AverageMMR,
                    };
                }
                else
                {
                    //Profile doesn't exist, so early abort.
                    return(null);
                }
            }


            //if we are updating, then lets do so
            if (Profile != null)
            {
                //Create a new history table
                var newHistory = new MMRHistory()
                {
                    Season  = Manager.Season,
                    Average = Profile.MetaData.AverageMMR,
                    Current = Profile.MetaData.MMR,
                    Minimum = prevHistory.Minimum > Profile.MetaData.MMR ? Profile.MetaData.MMR : prevHistory.Minimum,
                    Maximum = prevHistory.Maximum < Profile.MetaData.BestMMR ? Profile.MetaData.BestMMR : prevHistory.Maximum
                };

                //Check for rank changes
                if (newHistory.MaximumRank > prevHistory.MaximumRank)
                {
                    OnSeasonHigh?.Invoke(this, prevHistory, newHistory);
                }

                if (newHistory.Rank != prevHistory.Rank)
                {
                    OnRankChange?.Invoke(this, prevHistory, newHistory);
                }

                //Store the new history and assign it
                prevHistory = newHistory;
                await Manager.Redis.StoreObjectAsync(cacheName, prevHistory);
            }

            //Return the history
            return(MMRHistory = prevHistory);
        }
Esempio n. 23
0
 /// <summary>Edits the tag</summary>
 public async Task EditTagAsync(DiscordGuild guild, Tag tag, string content) => await Redis.StoreStringAsync(Namespace.Combine(guild.Id, "tags", tag.Name), Tag.KEY_CONTENT, content);
Esempio n. 24
0
        /// <summary>
        /// Links a user to the account name and returns the profile
        /// </summary>
        /// <param name="user"></param>
        /// <param name="accountName"></param>
        /// <returns></returns>
        public async Task <SiegeProfile> LinkProfileAsync(DiscordUser user, string accountName)
        {
            await Redis.StoreStringAsync(Namespace.Combine(RedisPrefix, "links"), user.Id.ToString(), accountName);

            return(await GetProfileAsync(accountName));
        }
Esempio n. 25
0
        /// <summary>
        /// Removes all the reaction roles. Doesn't remove any reactions already given.
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public async Task <bool> RemoveAllReactionRoleAsync(DiscordMessage message)
        {
            string key = Namespace.Combine(message.Channel.Guild, REDIS_REACT_ROLE, message);

            return(await Redis.RemoveAsync(key));
        }
Esempio n. 26
0
        public async Task <bool> DeleteReplyAsync(DiscordGuild guild, Reply reply)
        {
            string key = Namespace.Combine(guild.Id, "replies", reply.CommandMsg);

            return(await Redis.RemoveAsync(key));
        }
Esempio n. 27
0
 /// <summary>
 /// Links a emoji to a role.
 /// </summary>
 /// <param name="guild"></param>
 /// <param name="emoji"></param>
 /// <returns></returns>
 public async Task AddReactionRoleAsync(DiscordMessage message, DiscordEmoji emoji, DiscordRole role)
 {
     string key = Namespace.Combine(message.Channel.Guild, REDIS_REACT_ROLE, message);
     await Redis.StoreStringAsync(key, emoji.Id.ToString(), role.Id.ToString());
 }
Esempio n. 28
0
 /// <summary>
 /// Gets the namespace that the reply would be stored under.
 /// </summary>
 /// <param name="guildId"></param>
 /// <param name="messageId"></param>
 /// <returns></returns>
 private string GetReplyRedisNamespace(ulong guildId, ulong messageId) => Namespace.Combine(guildId, "replies", messageId);
Esempio n. 29
0
        /// <summary>
        /// Unlinks a emoji from a role
        /// </summary>
        /// <param name="guild"></param>
        /// <param name="emoji"></param>
        /// <returns></returns>
        public async Task <bool> RemoveReactionRoleAsync(DiscordMessage message, DiscordEmoji emoji)
        {
            string key = Namespace.Combine(message.Channel.Guild, REDIS_REACT_ROLE, message);

            return(await Redis.RemoveHashSetAsync(key, emoji.Id.ToString()));
        }