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); }
/// <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(); }
/// <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(); }
/// <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)); }
/// <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); }
/// <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))); }
/// <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); }
/// <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); }
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>()); }
/// <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); }
/// <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); }
/// <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); }
/// <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); }
/// <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); }
/// <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); } }
/// <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); }
/// <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); }
/// <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); }
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); } }
/// <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(); } }
/// <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)); }
/// <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); }
/// <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);
/// <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)); }
/// <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)); }
public async Task <bool> DeleteReplyAsync(DiscordGuild guild, Reply reply) { string key = Namespace.Combine(guild.Id, "replies", reply.CommandMsg); return(await Redis.RemoveAsync(key)); }
/// <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()); }
/// <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);
/// <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())); }