diff --git a/Discord.Addons.Interactive/InlineReaction/ReactionCallbackData.cs b/Discord.Addons.Interactive/InlineReaction/ReactionCallbackData.cs index 68744ae2..67c29585 100644 --- a/Discord.Addons.Interactive/InlineReaction/ReactionCallbackData.cs +++ b/Discord.Addons.Interactive/InlineReaction/ReactionCallbackData.cs @@ -24,7 +24,7 @@ public ReactionCallbackData(string text, Embed embed = null, bool expiresAfterUs SingleUsePerUser = singleUsePerUser; ExpiresAfterUse = expiresAfterUse; ReactorIDs = new List(); - Text = text ?? ""; + Text = text ?? string.Empty; Embed = embed; Timeout = timeout; TimeoutCallback = timeoutCallback; diff --git a/Floofbot/Configs/BotConfig.cs b/Floofbot/Configs/BotConfig.cs index 65e34cb3..598b3ffe 100644 --- a/Floofbot/Configs/BotConfig.cs +++ b/Floofbot/Configs/BotConfig.cs @@ -19,8 +19,8 @@ class BotConfig public string BackupScript { get; set; } public int NumberOfBackups { get; set; } public List AnnouncementChannels { get; set; } - public Dictionary RaidProtection { get; set; } - public Dictionary RulesGate { get; set; } + public Dictionary RaidProtection { get; set; } + public Dictionary RulesGate { get; set; } } } diff --git a/Floofbot/Configs/BotConfigFactory.cs b/Floofbot/Configs/BotConfigFactory.cs index b1a2a240..a52946d8 100644 --- a/Floofbot/Configs/BotConfigFactory.cs +++ b/Floofbot/Configs/BotConfigFactory.cs @@ -17,11 +17,11 @@ public static BotConfig Config [MethodImpl(MethodImplOptions.Synchronized)] get { - if (_config == null) - { - _config = BotConfigParser.ParseFromFile(_filename); - _token = _config.Token; - } + if (_config != null) return _config; + + _config = BotConfigParser.ParseFromFile(_filename); + _token = _config.Token; + return _config; } [MethodImpl(MethodImplOptions.Synchronized)] @@ -38,8 +38,9 @@ public static void Initialize(string filename) public static void Reinitialize() { - BotConfig config = BotConfigParser.ParseFromFile(_filename); - // sanity check to make sure the token was not changed upon reload + var config = BotConfigParser.ParseFromFile(_filename); + + // Sanity check to make sure the token was not changed upon reload if (config.Token == _token) { _config = config; @@ -50,7 +51,7 @@ public static void Reinitialize() } } - class BotConfigParser + private class BotConfigParser { public static BotConfig ParseFromFile(string filename) { @@ -59,11 +60,13 @@ public static BotConfig ParseFromFile(string filename) var fileContents = new StringReader(File.ReadAllText(filename)); var deserializer = new Deserializer(); var config = deserializer.Deserialize(fileContents); + return config; } catch (Exception e) { Log.Error(e.ToString()); + return new BotConfig(); } } diff --git a/Floofbot/Handlers/CommandHandler.cs b/Floofbot/Handlers/CommandHandler.cs index 8b535f67..de4ac052 100644 --- a/Floofbot/Handlers/CommandHandler.cs +++ b/Floofbot/Handlers/CommandHandler.cs @@ -24,92 +24,105 @@ public class CommandHandler public CommandHandler(DiscordSocketClient client) { - _client = client; - _services = BuildServiceProvider(client); var context = _services.GetRequiredService(); context.Database.Migrate(); // apply all migrations + + _client = client; + _services = BuildServiceProvider(client); + _commands = new CommandService(new CommandServiceConfig { CaseSensitiveCommands = false }); _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); + _client.MessageReceived += HandleCommandAsync; _client.MessageUpdated += OnMessageUpdatedHandler; } private IServiceProvider BuildServiceProvider(DiscordSocketClient client) { - InteractiveService interactiveService = new InteractiveService(client); + var interactiveService = new InteractiveService(client); + return new ServiceCollection() - .AddSingleton(interactiveService) + .AddSingleton(interactiveService) .AddDbContext() .BuildServiceProvider(); } - private Embed generateErrorEmbed(SocketUser user, IResult result, SocketUserMessage msg) + private Embed GenerateErrorEmbed(SocketUser user, IResult result, SocketUserMessage msg) { - EmbedAuthorBuilder author = new EmbedAuthorBuilder(); + var author = new EmbedAuthorBuilder(); + author.Name = user.Username + "#" + user.Discriminator; + if (Uri.IsWellFormedUriString(user.GetAvatarUrl(), UriKind.Absolute)) author.IconUrl = user.GetAvatarUrl(); + if (msg.Channel.GetType() != typeof(SocketDMChannel)) author.Url = msg.GetJumpUrl(); - EmbedBuilder builder = new EmbedBuilder - { - Author = author, - Title = "A fatal error has occured. User message content: " + msg.Content, - Description = result.Error + "\n```" + result.ErrorReason + "```", - Color = Color.Red - }; - builder.AddField("Channel Type", (msg.Channel.GetType() == typeof(SocketDMChannel) ? "DM" : "Guild") + " Channel"); - - builder.WithCurrentTimestamp(); + var builder = new EmbedBuilder + { + Author = author, + Title = "A fatal error has occured. User message content: " + msg.Content, + Description = result.Error + "\n```" + result.ErrorReason + "```", + Color = Color.Red + }.AddField("Channel Type", (msg.Channel.GetType() == typeof(SocketDMChannel) ? "DM" : "Guild") + " Channel") + .WithCurrentTimestamp(); + return builder.Build(); } private async Task LogErrorInDiscordChannel(IResult result, SocketMessage originalMessage) { - FloofDataContext _floofDb = new FloofDataContext(); - - var userMsg = originalMessage as SocketUserMessage; // the original command - var channel = userMsg.Channel as ITextChannel; // the channel of the original command - if (channel == null) - return; - - var serverConfig = _floofDb.ErrorLoggingConfigs.Find(channel.GuildId); // no db result - if (serverConfig == null) - return; - - if ((!serverConfig.IsOn) || (serverConfig.ChannelId == null)) // not configured or disabled - return; - - Discord.ITextChannel errorLoggingChannel = await channel.Guild.GetTextChannelAsync((ulong)serverConfig.ChannelId); // can return null if channel invalid - if (errorLoggingChannel == null) - return; - - - Embed embed = generateErrorEmbed(userMsg.Author, result, userMsg); - await errorLoggingChannel.SendMessageAsync("", false, embed); - return; + await using (var floofDb = new FloofDataContext()) + { + var userMsg = originalMessage as SocketUserMessage; // the original command + var channel = userMsg.Channel as ITextChannel; // the channel of the original command + + if (channel == null) + return; + + var serverConfig = await floofDb.ErrorLoggingConfigs.FindAsync(channel.GuildId); // no db result + + if (serverConfig == null) + return; + + if (!serverConfig.IsOn || (serverConfig.ChannelId == null)) // not configured or disabled + return; + + var errorLoggingChannel = await channel.Guild.GetTextChannelAsync((ulong)serverConfig.ChannelId); // can return null if channel invalid + + if (errorLoggingChannel == null) + return; + + var embed = GenerateErrorEmbed(userMsg.Author, result, userMsg); + + await errorLoggingChannel.SendMessageAsync(string.Empty, false, embed); + } } + private async Task OnMessageUpdatedHandler(Cacheable before, SocketMessage after, ISocketMessageChannel chan) { var messageBefore = before.Value as IUserMessage; + if (messageBefore == null) return; if (messageBefore.Content == after.Content) return; - if (messageBefore.EditedTimestamp == null) // user has never edited their message + if (messageBefore.EditedTimestamp == null) // User has never edited their message { var timeDifference = DateTimeOffset.Now - messageBefore.Timestamp; + if (timeDifference.TotalSeconds < 30) await HandleCommandAsync(after); - } } - private async Task HandleCommandAsync(SocketMessage s) + + private async Task HandleCommandAsync(SocketMessage socketMessage) { - var msg = s as SocketUserMessage; + var msg = socketMessage as SocketUserMessage; + if (msg == null) return; @@ -117,8 +130,8 @@ private async Task HandleCommandAsync(SocketMessage s) return; var context = new SocketCommandContext(_client, msg); - int argPos = 0; - string prefix; + var argPos = 0; + var prefix = string.Empty; if (string.IsNullOrEmpty(BotConfigFactory.Config.Prefix)) { @@ -130,55 +143,66 @@ private async Task HandleCommandAsync(SocketMessage s) prefix = BotConfigFactory.Config.Prefix; } - bool hasValidPrefix = msg.HasMentionPrefix(_client.CurrentUser, ref argPos) || msg.HasStringPrefix(prefix, ref argPos); - string strippedCommandName = msg.Content.Substring(argPos).Split()[0]; - bool hasValidStart = !string.IsNullOrEmpty(strippedCommandName) && Regex.IsMatch(strippedCommandName, @"^[0-9]?[a-z]+\??$", RegexOptions.IgnoreCase); + var hasValidPrefix = msg.HasMentionPrefix(_client.CurrentUser, ref argPos) || msg.HasStringPrefix(prefix, ref argPos); + var strippedCommandName = msg.Content.Substring(argPos).Split()[0]; + var hasValidStart = !string.IsNullOrEmpty(strippedCommandName) && Regex.IsMatch(strippedCommandName, @"^[0-9]?[a-z]+\??$", RegexOptions.IgnoreCase); + if (hasValidPrefix && hasValidStart) { var result = await _commands.ExecuteAsync(context, argPos, _services); if (!result.IsSuccess) { - string errorMessage = "An unknown exception occured. I have notified the administrators."; - bool isCriticalFailure = false; + var errorMessage = "An unknown exception occured. I have notified the administrators."; + var isCriticalFailure = false; + switch (result.Error) { case CommandError.BadArgCount: errorMessage = result.ErrorReason; break; + case CommandError.MultipleMatches: errorMessage = "Multiple results. I don't know which one you want! Please try to be more specific."; break; + case CommandError.ObjectNotFound: errorMessage = "One or more of your command parameters could not be resolved - " + result.ErrorReason; break; + case CommandError.ParseFailed: errorMessage = "For some reason, I can't understand your command."; break; + case CommandError.UnknownCommand: - // check 8ball response + // Check 8ball response if (msg.HasMentionPrefix(_client.CurrentUser, ref argPos) && msg.Content.EndsWith("?")) { - string eightBallResponse = Floofbot.Modules.Helpers.EightBall.GetRandomResponse(); + var eightBallResponse = Floofbot.Modules.Helpers.EightBall.GetRandomResponse(); - Embed embed = new EmbedBuilder() + var embed = new EmbedBuilder() { Description = msg.Content }.Build(); + await msg.Channel.SendMessageAsync($"{msg.Author.Mention} {eightBallResponse}", false, embed); return; } errorMessage = "Unknown command '" + strippedCommandName + "'. Please check your spelling and try again."; break; + case CommandError.UnmetPrecondition: errorMessage = "A command condition has not been met - " + result.ErrorReason; break; + default: await LogErrorInDiscordChannel(result, msg); isCriticalFailure = true; break; } + await msg.Channel.SendMessageAsync("ERROR: ``" + errorMessage + "``"); + if (isCriticalFailure) Log.Error(result.Error + "\nMessage Content: " + msg.Content + "\nError Reason: " + result.ErrorReason); else diff --git a/Floofbot/Handlers/RandomResponseGenerator.cs b/Floofbot/Handlers/RandomResponseGenerator.cs index 5c7429e5..eb46341e 100644 --- a/Floofbot/Handlers/RandomResponseGenerator.cs +++ b/Floofbot/Handlers/RandomResponseGenerator.cs @@ -8,25 +8,27 @@ class RandomResponseGenerator { public static string GenerateResponse(SocketUserMessage userMessage) { - // System messages (e.x. pin notifications) if (userMessage == null) { return string.Empty; } - List responses = BotConfigFactory.Config.RandomResponses; + var responses = BotConfigFactory.Config.RandomResponses; + if (responses == null || responses.Count == 0) { return string.Empty; } - Random rand = new Random(); - double val = rand.NextDouble(); + var rand = new Random(); + var val = rand.NextDouble(); + foreach (var response in responses) { - Regex requiredInput = new Regex(response.Input, RegexOptions.IgnoreCase); - Match match = requiredInput.Match(userMessage.Content); + var requiredInput = new Regex(response.Input, RegexOptions.IgnoreCase); + var match = requiredInput.Match(userMessage.Content); + if (match.Success && val < response.Probability) { if (match.Groups.Count == 1) { @@ -34,14 +36,17 @@ public static string GenerateResponse(SocketUserMessage userMessage) return response.Response; } - List matchedValues = new List(match.Groups.Count - 1); + var matchedValues = new List(match.Groups.Count - 1); + for (int i = 1; i < match.Groups.Count; i++) { matchedValues.Add(match.Groups[i].Value); } + return string.Format(response.Response, matchedValues.ToArray()); } } + return string.Empty; } } diff --git a/Floofbot/Modules/Administration.cs b/Floofbot/Modules/Administration.cs index be1fa5d6..7d843515 100644 --- a/Floofbot/Modules/Administration.cs +++ b/Floofbot/Modules/Administration.cs @@ -11,7 +11,6 @@ using Discord.Addons.Interactive; using Microsoft.EntityFrameworkCore; using Serilog; -using System.Drawing.Printing; using Discord.Net; namespace Floofbot.Modules @@ -22,9 +21,9 @@ public class Administration : InteractiveBase { private static readonly Color ADMIN_COLOR = Color.DarkOrange; private static readonly int MESSAGES_TO_SCAN_PER_CHANNEL_ON_PURGE = 100; - private FloofDataContext _floofDB; + private FloofDataContext _floofDb; - public Administration(FloofDataContext floofDB) => _floofDB = floofDB; + public Administration(FloofDataContext floofDb) => _floofDb = floofDb; [Command("ban")] [Alias("b")] @@ -35,65 +34,75 @@ public async Task YeetUser( [Summary("user")] string user, [Summary("reason")][Remainder] string reason = "No Reason Provided") { - IUser badUser = resolveUser(user); + var badUser = ResolveUser(user); + if (badUser == null) { if (Regex.IsMatch(user, @"\d{16,}")) { - string userID = Regex.Match(user, @"\d{16,}").Value; - if (_floofDB.BansOnJoin.AsQueryable().Any(u => u.UserID == Convert.ToUInt64(userID))) // user is already going to be banned when they join + var userId = Regex.Match(user, @"\d{16,}").Value; + + if (_floofDb.BansOnJoin.AsQueryable().Any(u => u.UserID == Convert.ToUInt64(userId))) // user is already going to be banned when they join { await Context.Channel.SendMessageAsync("⚠️ Cannot find user - they are already going to be banned when they join!"); + return; } - else + + _floofDb.Add(new BanOnJoin { - _floofDB.Add(new BanOnJoin - { - UserID = Convert.ToUInt64(userID), - ModID = Context.Message.Author.Id, - ModUsername = $"{Context.Message.Author.Username}#{Context.Message.Author.Discriminator}", - Reason = reason - }); - _floofDB.SaveChanges(); - await Context.Channel.SendMessageAsync("⚠️ Could not find user, they will be banned next time they join the server!"); - return; - } - } - else - { - await Context.Channel.SendMessageAsync($"⚠️ Could not resolve user: \"{user}\""); + UserID = Convert.ToUInt64(userId), + ModID = Context.Message.Author.Id, + ModUsername = $"{Context.Message.Author.Username}#{Context.Message.Author.Discriminator}", + Reason = reason + }); + + await _floofDb.SaveChangesAsync(); + + await Context.Channel.SendMessageAsync("⚠️ Could not find user, they will be banned next time they join the server!"); + return; } + + await Context.Channel.SendMessageAsync($"⚠️ Could not resolve user: \"{user}\""); } try { - //sends message to user - EmbedBuilder builder = new EmbedBuilder(); - builder.Title = "⚖️ Ban Notification"; - builder.Description = $"You have been banned from {Context.Guild.Name}"; - builder.AddField("Reason", reason); - builder.Color = ADMIN_COLOR; - await badUser.SendMessageAsync("", false, builder.Build()); + // Sends message to user + var builder = new EmbedBuilder + { + Title = "⚖️ Ban Notification", + Description = $"You have been banned from {Context.Guild.Name}", + Color = ADMIN_COLOR + }.AddField("Reason", reason); + + await badUser.SendMessageAsync(string.Empty, false, builder.Build()); } catch (HttpException) { await Context.Channel.SendMessageAsync("⚠️ | Unable to DM user to notify them of their ban!"); } - //bans the user - await Context.Guild.AddBanAsync(badUser.Id, 0, $"{Context.User.Username}#{Context.User.Discriminator} -> {reason}"); + // Bans the user + if (badUser != null) + { + await Context.Guild.AddBanAsync(badUser.Id, 0, + $"{Context.User.Username}#{Context.User.Discriminator} -> {reason}"); - EmbedBuilder modEmbedBuilder = new EmbedBuilder(); - modEmbedBuilder = new EmbedBuilder(); - modEmbedBuilder.Title = (":shield: User Banned"); - modEmbedBuilder.Color = ADMIN_COLOR; - modEmbedBuilder.Description = $"{badUser.Username}#{badUser.Discriminator} has been banned from {Context.Guild.Name}"; - modEmbedBuilder.AddField("User ID", badUser.Id); - modEmbedBuilder.AddField("Moderator", $"{Context.User.Username}#{Context.User.Discriminator}"); - await Context.Channel.SendMessageAsync("", false, modEmbedBuilder.Build()); + var modEmbedBuilder = new EmbedBuilder + { + Title = (":shield: User Banned"), + Color = ADMIN_COLOR, + Description = + $"{badUser.Username}#{badUser.Discriminator} has been banned from {Context.Guild.Name}" + }.AddField("User ID", badUser.Id) + .AddField("Moderator", $"{Context.User.Username}#{Context.User.Discriminator}"); + await Context.Channel.SendMessageAsync(string.Empty, false, modEmbedBuilder.Build()); + } + + await Context.Channel.SendMessageAsync($"⚠️ Could not resolve user: \"{user}\""); } [Command("pruneban")] @@ -101,69 +110,72 @@ public async Task YeetUser( [Summary("Bans a user from the server, with an optional reason and prunes their messages")] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.BanMembers)] - public async Task pruneBanUser( + public async Task PruneBanUser( [Summary("user")] string user, [Summary("Number Of Days To Prune")] int pruneDays = 0, [Summary("reason")][Remainder] string reason = "No Reason Provided") { - IUser badUser = resolveUser(user); + var badUser = ResolveUser(user); + if (badUser == null) { if (Regex.IsMatch(user, @"\d{16,}")) { - string userID = Regex.Match(user, @"\d{16,}").Value; - if (_floofDB.BansOnJoin.AsQueryable().Any(u => u.UserID == Convert.ToUInt64(userID))) // user is already going to be banned when they join + var userId = Regex.Match(user, @"\d{16,}").Value; + + if (_floofDb.BansOnJoin.AsQueryable().Any(u => u.UserID == Convert.ToUInt64(userId))) // User is already going to be banned when they join { await Context.Channel.SendMessageAsync("⚠️ Cannot find user - they are already going to be banned when they join!"); return; } - else + + _floofDb.Add(new BanOnJoin { - _floofDB.Add(new BanOnJoin - { - UserID = Convert.ToUInt64(userID), - ModID = Context.Message.Author.Id, - ModUsername = $"{Context.Message.Author.Username}#{Context.Message.Author.Discriminator}", - Reason = reason - }); - _floofDB.SaveChanges(); - await Context.Channel.SendMessageAsync("⚠️ Could not find user, they will be banned next time they join the server!"); - return; - } - } - else - { - await Context.Channel.SendMessageAsync($"⚠️ Could not resolve user: \"{user}\""); + UserID = Convert.ToUInt64(userId), + ModID = Context.Message.Author.Id, + ModUsername = $"{Context.Message.Author.Username}#{Context.Message.Author.Discriminator}", + Reason = reason + }); + + await _floofDb.SaveChangesAsync(); + + await Context.Channel.SendMessageAsync("⚠️ Could not find user, they will be banned next time they join the server!"); return; } + + await Context.Channel.SendMessageAsync($"⚠️ Could not resolve user: \"{user}\""); + return; } try { - //sends message to user - EmbedBuilder builder = new EmbedBuilder(); - builder.Title = "⚖️ Ban Notification"; - builder.Description = $"You have been banned from {Context.Guild.Name}"; - builder.AddField("Reason", reason); - builder.Color = ADMIN_COLOR; - await badUser.SendMessageAsync("", false, builder.Build()); + // Sends message to user + var builder = new EmbedBuilder + { + Title = "⚖️ Ban Notification", + Description = $"You have been banned from {Context.Guild.Name}", + Color = ADMIN_COLOR + }.AddField("Reason", reason); + + await badUser.SendMessageAsync(string.Empty, false, builder.Build()); } catch (HttpException) { await Context.Channel.SendMessageAsync("⚠️ | Unable to DM user to notify them of their ban!"); } - //bans the user + // Bans the user await Context.Guild.AddBanAsync(badUser.Id, pruneDays, $"{Context.User.Username}#{Context.User.Discriminator} -> {reason}"); - EmbedBuilder modEmbedBuilder = new EmbedBuilder(); - modEmbedBuilder = new EmbedBuilder(); - modEmbedBuilder.Title = (":shield: User Banned"); - modEmbedBuilder.Color = ADMIN_COLOR; - modEmbedBuilder.Description = $"{badUser.Username}#{badUser.Discriminator} has been banned from {Context.Guild.Name}"; - modEmbedBuilder.AddField("User ID", badUser.Id); - modEmbedBuilder.AddField("Moderator", $"{Context.User.Username}#{Context.User.Discriminator}"); - await Context.Channel.SendMessageAsync("", false, modEmbedBuilder.Build()); + var modEmbedBuilder = new EmbedBuilder + { + Title = (":shield: User Banned"), + Color = ADMIN_COLOR, + Description = $"{badUser.Username}#{badUser.Discriminator} has been banned from {Context.Guild.Name}" + }.AddField("User ID", badUser.Id) + .AddField("Moderator", $"{Context.User.Username}#{Context.User.Discriminator}"); + + await Context.Channel.SendMessageAsync(string.Empty, false, modEmbedBuilder.Build()); } [Command("viewautobans")] @@ -172,35 +184,39 @@ public async Task pruneBanUser( [RequireContext(ContextType.Guild)] public async Task ViewAutoBans() { - if (!_floofDB.BansOnJoin.AsQueryable().Any()) // there are no auto bans! + if (!_floofDb.BansOnJoin.AsQueryable().Any()) // there are no auto bans! { await Context.Channel.SendMessageAsync("There are no auto bans configured!"); return; } - List autoBans = _floofDB.BansOnJoin.AsQueryable().ToList(); + + var autoBans = _floofDb.BansOnJoin.AsQueryable().ToList(); + var pages = new List(); + var numPages = (int) Math.Ceiling((double) autoBans.Count / 20); - List pages = new List(); - int numPages = (int)Math.Ceiling((double)autoBans.Count / 20); - int index; for (int i = 0; i < numPages; i++) { - string text = "```\n"; + var text = "```\n"; + for (int j = 0; j < 20; j++) { - index = i * 20 + j; + var index = (i * 20) + j; + if (index < autoBans.Count) { - var modUser = resolveUser(autoBans[index].ModID.ToString()); // try to resolve the mod who added it + var modUser = ResolveUser(autoBans[index].ModID.ToString()); // try to resolve the mod who added it var modUsername = ((modUser != null) ? $"{modUser.Username}#{modUser.Discriminator}" : $"{autoBans[index].ModUsername}"); // try to get mod's new username, otherwise, use database stored name text += $"{index + 1}. {autoBans[index].UserID} - added by {modUsername}\n"; } } + text += "\n```"; + pages.Add(new PaginatedMessage.Page { Description = text }); - }; + } var pager = new PaginatedMessage { @@ -211,6 +227,7 @@ public async Task ViewAutoBans() Options = PaginatedAppearanceOptions.Default, TimeStamp = DateTimeOffset.UtcNow }; + await PagedReplyAsync(pager, new ReactionList { Forward = true, @@ -231,23 +248,25 @@ public async Task RemoveAutoBan([Summary("user ID")] string userId) await Context.Channel.SendMessageAsync("This is not a valid user ID! Please specify the User ID you wish to remove from the auto ban list!"); return; } - BanOnJoin user = _floofDB.BansOnJoin.AsQueryable().Where(u => u.UserID == Convert.ToUInt64(userId)).FirstOrDefault(); + + var user = _floofDb.BansOnJoin.AsQueryable().FirstOrDefault(u => u.UserID == Convert.ToUInt64(userId)); + if (user == null) // there are no auto bans! { await Context.Channel.SendMessageAsync("This user is not in the auto ban list!"); return; } + try { - _floofDB.Remove(user); - await _floofDB.SaveChangesAsync(); + _floofDb.Remove(user); + + await _floofDb.SaveChangesAsync(); await Context.Channel.SendMessageAsync($"{userId} will no longer be automatically banned when they join the server!"); - return; } catch (DbUpdateException) // db error { await Context.Channel.SendMessageAsync($"Unable to remove {userId} from the database."); - return; } } @@ -256,24 +275,29 @@ public async Task RemoveAutoBan([Summary("user ID")] string userId) [Summary("Kicks a user from the server, with an optional reason")] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.KickMembers)] - public async Task kickUser( + public async Task KickUser( [Summary("user")] string user, [Summary("reason")][Remainder] string reason = "No Reason Provided") { - IUser badUser = resolveUser(user); + var badUser = ResolveUser(user); + if (badUser == null) { await Context.Channel.SendMessageAsync($"⚠️ Could not resolve user: \"{user}\""); + return; } + try { //sends message to user - EmbedBuilder builder = new EmbedBuilder(); - builder.Title = "🥾 Kick Notification"; - builder.Description = $"You have been Kicked from {Context.Guild.Name}"; - builder.AddField("Reason", reason); - builder.Color = ADMIN_COLOR; - await badUser.SendMessageAsync("", false, builder.Build()); + var builder = new EmbedBuilder + { + Title = "🥾 Kick Notification", + Description = $"You have been Kicked from {Context.Guild.Name}", + Color = ADMIN_COLOR + }.AddField("Reason", reason); + + await badUser.SendMessageAsync(string.Empty, false, builder.Build()); } catch (HttpException) { @@ -282,13 +306,15 @@ public async Task kickUser( //kicks users await Context.Guild.GetUser(badUser.Id).KickAsync(reason); - EmbedBuilder kickBuilder = new EmbedBuilder(); - kickBuilder.Title = ("🥾 User Kicked"); - kickBuilder.Color = ADMIN_COLOR; - kickBuilder.Description = $"{badUser.Username}#{badUser.Discriminator} has been kicked from {Context.Guild.Name}"; - kickBuilder.AddField("User ID", badUser.Id); - kickBuilder.AddField("Moderator", $"{Context.User.Username}#{Context.User.Discriminator}"); - await Context.Channel.SendMessageAsync("", false, kickBuilder.Build()); + var kickBuilder = new EmbedBuilder + { + Title = ("🥾 User Kicked"), + Color = ADMIN_COLOR, + Description = $"{badUser.Username}#{badUser.Discriminator} has been kicked from {Context.Guild.Name}" + }.AddField("User ID", badUser.Id) + .AddField("Moderator", $"{Context.User.Username}#{Context.User.Discriminator}"); + + await Context.Channel.SendMessageAsync(string.Empty, false, kickBuilder.Build()); } [Command("silentkick")] @@ -296,26 +322,29 @@ public async Task kickUser( [Summary("Kicks a user from the server. Does not notify the user.")] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.KickMembers)] - public async Task silentKickUser( + public async Task SilentKickUser( [Summary("user")] string user, [Summary("reason")][Remainder] string reason = "No Reason Provided") { - IUser badUser = resolveUser(user); + var badUser = ResolveUser(user); + if (badUser == null) { await Context.Channel.SendMessageAsync($"⚠️ Could not resolve user: \"{user}\""); return; } - //kicks users + // Kicks users await Context.Guild.GetUser(badUser.Id).KickAsync(reason); - EmbedBuilder kickBuilder = new EmbedBuilder(); - kickBuilder.Title = ("🥾 User Silently Kicked"); - kickBuilder.Color = ADMIN_COLOR; - kickBuilder.Description = $"{badUser.Username}#{badUser.Discriminator} has been silently kicked from {Context.Guild.Name}"; - kickBuilder.AddField("User ID", badUser.Id); - kickBuilder.AddField("Moderator", $"{Context.User.Username}#{Context.User.Discriminator}"); - await Context.Channel.SendMessageAsync("", false, kickBuilder.Build()); + var kickBuilder = new EmbedBuilder + { + Title = ("🥾 User Silently Kicked"), + Color = ADMIN_COLOR, + Description = $"{badUser.Username}#{badUser.Discriminator} has been silently kicked from {Context.Guild.Name}" + }.AddField("User ID", badUser.Id) + .AddField("Moderator", $"{Context.User.Username}#{Context.User.Discriminator}"); + + await Context.Channel.SendMessageAsync(string.Empty, false, kickBuilder.Build()); } [Command("warn")] @@ -323,27 +352,32 @@ public async Task silentKickUser( [Summary("Warns a user on the server, with a given reason")] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.KickMembers)] - public async Task warnUser( + public async Task WarnUser( [Summary("user")] string user, [Summary("reason")][Remainder] string reason = "") { EmbedBuilder builder; + if (string.IsNullOrEmpty(reason)) { builder = new EmbedBuilder() { Description = $"Usage: `warn [user] [reason]`", Color = Color.Magenta }; - await Context.Channel.SendMessageAsync("", false, builder.Build()); + + await Context.Channel.SendMessageAsync(string.Empty, false, builder.Build()); + return; } if(reason.Length > 500) { await Context.Channel.SendMessageAsync("Warnings can not exceed 500 characters"); + return; } - IUser badUser = resolveUser(user); - ulong uid = 0; // used if no resolved user + var badUser = ResolveUser(user); + ulong uid; // used if no resolved user + if (badUser == null) { if (Regex.IsMatch(user, @"\d{16,}")) @@ -353,6 +387,7 @@ public async Task warnUser( else { await Context.Channel.SendMessageAsync($"⚠️ Could not find user \"{user}\""); + return; } } @@ -361,7 +396,7 @@ public async Task warnUser( uid = badUser.Id; } - _floofDB.Add(new Warning + _floofDb.Add(new Warning { DateAdded = DateTime.Now, Forgiven = false, @@ -372,19 +407,23 @@ public async Task warnUser( UserId = uid, warningUrl = Context.Message.GetJumpUrl() }) ; - _floofDB.SaveChanges(); + + await _floofDb.SaveChangesAsync(); if (badUser != null) // only send if resolved user { try { - //sends message to user - builder = new EmbedBuilder(); - builder.Title = "⚖️ Warn Notification"; - builder.Description = $"You have recieved a warning in {Context.Guild.Name}"; - builder.AddField("Reason", reason); - builder.Color = ADMIN_COLOR; - await badUser.SendMessageAsync("", false, builder.Build()); + // Sends message to user + + builder = new EmbedBuilder + { + Title = "⚖️ Warn Notification", + Description = $"You have recieved a warning in {Context.Guild.Name}", + Color = ADMIN_COLOR + }.AddField("Reason", reason); + + await badUser.SendMessageAsync(string.Empty, false, builder.Build()); } catch (HttpException) { @@ -392,13 +431,14 @@ public async Task warnUser( } } - builder = new EmbedBuilder(); - builder.Title = (":shield: User Warned"); - builder.Color = ADMIN_COLOR; - builder.AddField("User ID", uid); - builder.AddField("Moderator", $"{Context.User.Username}#{Context.User.Discriminator}"); + builder = new EmbedBuilder + { + Title = (":shield: User Warned"), + Color = ADMIN_COLOR + }.AddField("User ID", uid) + .AddField("Moderator", $"{Context.User.Username}#{Context.User.Discriminator}"); - await Context.Channel.SendMessageAsync("", false, builder.Build()); + await Context.Channel.SendMessageAsync(string.Empty, false, builder.Build()); } [Command("usernote")] @@ -406,27 +446,32 @@ public async Task warnUser( [Summary("Add a moderation-style user note, give a specified reason")] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.KickMembers)] - public async Task userNote( + public async Task UserNote( [Summary("user")] string user, [Summary("reason")][Remainder] string reason = "") { EmbedBuilder builder; + if (string.IsNullOrEmpty(reason)) { - builder = new EmbedBuilder() { - Description = $"Usage: `usernote [user] [reason]`", + builder = new EmbedBuilder { + Description = "Usage: `usernote [user] [reason]`", Color = Color.Magenta }; - await Context.Channel.SendMessageAsync("", false, builder.Build()); + + await Context.Channel.SendMessageAsync(string.Empty, false, builder.Build()); + return; } if(reason.Length > 500) { await Context.Channel.SendMessageAsync("User notes can not exceed 500 characters"); + return; } - IUser badUser = resolveUser(user); - ulong uid = 0; // used if no resolved user + var badUser = ResolveUser(user); + ulong uid; // used if no resolved user + if (badUser == null) { if (Regex.IsMatch(user, @"\d{16,}")) @@ -444,7 +489,7 @@ public async Task userNote( uid = badUser.Id; } - _floofDB.Add(new UserNote { + _floofDb.Add(new UserNote { DateAdded = DateTime.Now, Forgiven = false, GuildId = Context.Guild.Id, @@ -453,15 +498,17 @@ public async Task userNote( Reason = reason, UserId = uid }); - _floofDB.SaveChanges(); + + await _floofDb.SaveChangesAsync(); - builder = new EmbedBuilder(); - builder.Title = (":pencil: User Note Added"); - builder.Color = ADMIN_COLOR; - builder.AddField("User ID", uid); - builder.AddField("Moderator", $"{Context.User.Username}#{Context.User.Discriminator}"); + builder = new EmbedBuilder + { + Title = (":pencil: User Note Added"), + Color = ADMIN_COLOR + }.AddField("User ID", uid) + .AddField("Moderator", $"{Context.User.Username}#{Context.User.Discriminator}"); - await Context.Channel.SendMessageAsync("", false, builder.Build()); + await Context.Channel.SendMessageAsync(string.Empty, false, builder.Build()); } [Command("purge")] @@ -473,7 +520,8 @@ public async Task PurgeUserMessages( [Summary("user")] string user) { string userId; - IUser badUser = resolveUser(user); + var badUser = ResolveUser(user); + if (badUser == null) { if (Regex.IsMatch(user, @"\d{16,}")) @@ -491,10 +539,11 @@ public async Task PurgeUserMessages( userId = badUser.Id.ToString(); } - // retrieve user messages from ALL channels + // Retrieve user messages from ALL channels foreach (ISocketMessageChannel channel in Context.Guild.TextChannels) { var asyncMessageCollections = channel.GetMessagesAsync(MESSAGES_TO_SCAN_PER_CHANNEL_ON_PURGE); + await foreach(var messageCollection in asyncMessageCollections) { foreach (var message in messageCollection) @@ -508,78 +557,91 @@ public async Task PurgeUserMessages( } } - EmbedBuilder builder = new EmbedBuilder(); - builder.Title = (":shield: Messages Purged"); - builder.Color = ADMIN_COLOR; - builder.AddField("User ID", badUser.Id); - builder.AddField("Moderator", $"{Context.User.Username}#{Context.User.Discriminator}"); + if (badUser != null) + { + var builder = new EmbedBuilder + { + Title = (":shield: Messages Purged"), + Color = ADMIN_COLOR + }.AddField("User ID", badUser.Id) + .AddField("Moderator", $"{Context.User.Username}#{Context.User.Discriminator}"); + + await Context.Channel.SendMessageAsync("", false, builder.Build()); - await Context.Channel.SendMessageAsync("", false, builder.Build()); + return; + } + + await Context.Channel.SendMessageAsync("⚠️ Cannot find user"); } [Command("warnlog")] [Alias("wl")] [Summary("Displays the warning log for a given user")] [RequireContext(ContextType.Guild)] - public async Task warnlog([Summary("user")] string user = "") + public async Task Warnlog([Summary("user")] string user = "") { - SocketGuildUser selfUser = Context.Guild.GetUser(Context.Message.Author.Id); // get the guild user + var selfUser = Context.Guild.GetUser(Context.Message.Author.Id); // Get the guild user Embed embed; - if (string.IsNullOrEmpty(user)) // want to view their own warnlog + + if (string.IsNullOrEmpty(user)) // Want to view their own warnlog { embed = GetWarnings(Context.Message.Author.Id, true); if (embed == null) - { return; - } - await Context.Message.Author.SendMessageAsync("", false, embed); + + await Context.Message.Author.SendMessageAsync(string.Empty, false, embed); + return; } - else // a mod + + if (selfUser.GuildPermissions.KickMembers) // Want to view their own warnlog { - if (selfUser.GuildPermissions.KickMembers) // want to view their own warnlog + var badUser = ResolveUser(user); + + if (badUser == null) { - IUser badUser = resolveUser(user); - if (badUser == null) - if (UInt64.TryParse(user, out ulong userid)) - embed = GetWarnings(userid, false); - else - { - await Context.Channel.SendMessageAsync("⚠️ Unable to find that user."); - return; - } + if (UInt64.TryParse(user, out ulong userid)) + { + embed = GetWarnings(userid, false); + } else - embed = GetWarnings(badUser.Id, false); - if (embed == null) { - return; + await Context.Channel.SendMessageAsync("⚠️ Unable to find that user."); + + return; } - await Context.Channel.SendMessageAsync("", false, embed); - return; } - else // mod wants to view another users log + else { - await Context.Channel.SendMessageAsync("Only moderators can view the warn logs of other users."); - return; + embed = GetWarnings(badUser.Id, false); } + + if (embed == null) + return; + + await Context.Channel.SendMessageAsync(string.Empty, false, embed); + + return; } + + // Mod wants to view another users log + await Context.Channel.SendMessageAsync("Only moderators can view the warn logs of other users."); } [Command("forgive", RunMode = RunMode.Async)] [Summary("Remove a user's warning or user notes")] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.KickMembers)] - public async Task forgiveUser([Summary("warning/usernote")] string type = "", [Summary("user")] string badUser = "") + public async Task ForgiveUser([Summary("warning/usernote")] string type = "", [Summary("user")] string badUser = "") { await UpdateForgivenStatus("forgiven", type, badUser); } - - + [Command("unforgive", RunMode = RunMode.Async)] [Summary("Unforgive a warning or user notes")] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.KickMembers)] - public async Task unforgiveUser([Summary("warning/usernote")] string type = "", [Summary("user")] string badUser = "") + public async Task UnforgiveUser([Summary("warning/usernote")] string type = "", [Summary("user")] string badUser = "") { await UpdateForgivenStatus("unforgiven", type, badUser); } @@ -590,7 +652,8 @@ public async Task unforgiveUser([Summary("warning/usernote")] string type = "", [RequireUserPermission(GuildPermission.ManageMessages)] public async Task MuteUser([Summary("user")]string user, [Summary("Time")]string time = null) { - IUser badUser = resolveUser(user); + var badUser = ResolveUser(user); + if (badUser == null) { await Context.Channel.SendMessageAsync($"⚠️ Could not find user \"{user}\""); return; @@ -598,64 +661,75 @@ public async Task MuteUser([Summary("user")]string user, [Summary("Time")]string IRole muteRole; - //check to see if the server exists within the "AdminConfig" Table - if (!_floofDB.AdminConfig.AsQueryable().Any(x => x.ServerId == Context.Guild.Id)) { - - //create new mute role + // Check to see if the server exists within the "AdminConfig" Table + if (!_floofDb.AdminConfig.AsQueryable().Any(x => x.ServerId == Context.Guild.Id)) { + // Create new mute role muteRole = await CreateMuteRole(); - //save the newly created role - _floofDB.Add(new AdminConfig { + // Save the newly created role + _floofDb.Add(new AdminConfig { ServerId = Context.Guild.Id, MuteRoleId = muteRole.Id }); - _floofDB.SaveChanges(); + + await _floofDb.SaveChangesAsync(); } else { - //grabs the mute role from the database + // Grabs the mute role from the database muteRole = Context.Guild.GetRole( - _floofDB.AdminConfig.AsQueryable() + _floofDb.AdminConfig.AsQueryable() .Where(x => x.ServerId == Context.Guild.Id) .Select(x => x.MuteRoleId).ToList()[0]); - //mute role was deleted create a new one + // Mute role was deleted create a new one if (muteRole == null) { muteRole = await CreateMuteRole(); - var result = _floofDB.AdminConfig.AsQueryable() - .SingleOrDefault(x => x.ServerId == Context.Guild.Id); - result.MuteRoleId = muteRole.Id; - _floofDB.SaveChanges(); + + var result = _floofDb.AdminConfig + .AsQueryable() + .SingleOrDefault(x => x.ServerId == Context.Guild.Id); + + if (result != null) + result.MuteRoleId = muteRole.Id; + + await _floofDb.SaveChangesAsync(); } } if (Context.Guild.GetUser(badUser.Id).Roles.Contains(muteRole)) { await Context.Channel.SendMessageAsync($"{badUser.Username}#{badUser.Discriminator} is already muted!"); + return; } await Context.Guild.GetUser(badUser.Id).AddRoleAsync(muteRole); - EmbedBuilder builder = new EmbedBuilder() { + var builder = new EmbedBuilder() { Title = "🔇 User Muted", Description = $"{badUser.Username}#{badUser.Discriminator} Muted!", Color = ADMIN_COLOR }; string durationNotifyString = null; + if (time != null) { - var m = Regex.Match(time, @"^((?\d+)d)?((?\d+)h)?((?\d+)m)?((?\d+)s)?$", RegexOptions.ExplicitCapture | RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.RightToLeft); + var m = Regex.Match(time, @"^((?\d+)d)?((?\d+)h)?((?\d+)m)?((?\d+)s)?$", + RegexOptions.ExplicitCapture + | RegexOptions.Compiled + | RegexOptions.CultureInvariant + | RegexOptions.RightToLeft); - int dd = m.Groups["days"].Success ? int.Parse(m.Groups["days"].Value) : 0; - int hs = m.Groups["hours"].Success ? int.Parse(m.Groups["hours"].Value) : 0; - int ms = m.Groups["minutes"].Success ? int.Parse(m.Groups["minutes"].Value) : 0; - int ss = m.Groups["seconds"].Success ? int.Parse(m.Groups["seconds"].Value) : 0; + var dd = m.Groups["days"].Success ? int.Parse(m.Groups["days"].Value) : 0; + var hs = m.Groups["hours"].Success ? int.Parse(m.Groups["hours"].Value) : 0; + var ms = m.Groups["minutes"].Success ? int.Parse(m.Groups["minutes"].Value) : 0; + var ss = m.Groups["seconds"].Success ? int.Parse(m.Groups["seconds"].Value) : 0; - int seconds = dd * 86400 + hs * 60 * 60 + ms * 60 + ss; + var seconds = (dd * 86400) + (hs * 60 * 60) + (ms * 60) + ss; if (seconds > 0) { - TimeSpan duration = TimeSpan.FromSeconds(seconds); + var duration = TimeSpan.FromSeconds(seconds); - string delayString = ""; + var delayString = String.Empty; if (duration.Days > 0) delayString += $"Days: {duration.Days} "; @@ -668,21 +742,26 @@ public async Task MuteUser([Summary("user")]string user, [Summary("Time")]string durationNotifyString = delayString; builder.AddField("Duration", delayString); - //unmute user after duration has expired - _ = Task.Run(async () => + + // Unmute user after duration has expired + await Task.Run(async () => { await Task.Delay(duration); if (Context.Guild.GetUser(badUser.Id).Roles.Contains(muteRole)) { await Context.Guild.GetUser(badUser.Id).RemoveRoleAsync(muteRole); + try { //notify user that they were unmuted - builder = new EmbedBuilder(); - builder.Title = "🔊 Unmute Notification"; - builder.Description = $"Your Mute on {Context.Guild.Name} has expired"; - builder.Color = ADMIN_COLOR; - await badUser.SendMessageAsync("", false, builder.Build()); + builder = new EmbedBuilder + { + Title = "🔊 Unmute Notification", + Description = $"Your Mute on {Context.Guild.Name} has expired", + Color = ADMIN_COLOR + }; + + await badUser.SendMessageAsync(string.Empty, false, builder.Build()); } catch (HttpException) { @@ -690,27 +769,32 @@ public async Task MuteUser([Summary("user")]string user, [Summary("Time")]string } } }); - } - else { + else + { await Context.Channel.SendMessageAsync("Invalid Time format... \nExamples: `.mute Talon#6237 1d` `.mute Talon#6237 6h30m`"); + return; } - } - await Context.Channel.SendMessageAsync("", false, builder.Build()); + + await Context.Channel.SendMessageAsync(string.Empty, false, builder.Build()); + try { - //notify user that they were muted - builder = new EmbedBuilder(); - builder.Title = "🔇 Mute Notification"; - builder.Description = $"You have been muted on {Context.Guild.Name}"; + // Notify user that they were muted + builder = new EmbedBuilder + { + Title = "🔇 Mute Notification", + Description = $"You have been muted on {Context.Guild.Name}" + }; if (durationNotifyString != null) builder.AddField("Duration", durationNotifyString); builder.Color = ADMIN_COLOR; - await badUser.SendMessageAsync("", false, builder.Build()); + + await badUser.SendMessageAsync(string.Empty, false, builder.Build()); } catch (HttpException) { @@ -718,17 +802,17 @@ public async Task MuteUser([Summary("user")]string user, [Summary("Time")]string } } - public async Task CreateMuteRole() + private async Task CreateMuteRole() { var muteRole = await Context.Guild.CreateRoleAsync("Muted", new GuildPermissions(), Color.DarkerGrey, false, false); - //add channel overrides for the new mute role + // Add channel overrides for the new mute role foreach (IGuildChannel channel in Context.Guild.Channels) { OverwritePermissions permissions = new OverwritePermissions( sendMessages: PermValue.Deny, addReactions: PermValue.Deny, speak: PermValue.Deny - ); + ); await channel.AddPermissionOverwriteAsync(muteRole, permissions); } @@ -742,20 +826,24 @@ public async Task CreateMuteRole() [RequireUserPermission(GuildPermission.ManageMessages)] public async Task UnmuteUser([Summary("user")]string user) { - IUser badUser = resolveUser(user); + var badUser = ResolveUser(user); + if (badUser == null) { await Context.Channel.SendMessageAsync($"⚠️ Could not find user \"{user}\""); + return; } var muteRole = Context.Guild.GetRole( - _floofDB.AdminConfig.AsQueryable() - .Where(x => x.ServerId == Context.Guild.Id) - .Select(x => x.MuteRoleId).ToList()[0]); + _floofDb.AdminConfig + .AsQueryable() + .Where(x => x.ServerId == Context.Guild.Id) + .Select(x => x.MuteRoleId).ToList()[0]); if (muteRole == null) { await Context.Channel.SendMessageAsync("The Mute Role for this Server Doesn't Exist!\n" + "A new one will be created next time you run the `mute` command"); + return; } @@ -764,24 +852,29 @@ public async Task UnmuteUser([Summary("user")]string user) } else { await Context.Channel.SendMessageAsync($"{badUser.Username}#{badUser.Discriminator} is not muted"); + return; } - EmbedBuilder builder = new EmbedBuilder() { + var builder = new EmbedBuilder() { Title = "🔊 User Unmuted", Description = $"{badUser.Username}#{badUser.Discriminator} was unmuted!", Color = ADMIN_COLOR }; - await Context.Channel.SendMessageAsync("", false, builder.Build()); + await Context.Channel.SendMessageAsync(string.Empty, false, builder.Build()); + try { - //notify user that they were unmuted - builder = new EmbedBuilder(); - builder.Title = "🔊 Unmute Notification"; - builder.Description = $"Your Mute on {Context.Guild.Name} has expired"; - builder.Color = ADMIN_COLOR; - await badUser.SendMessageAsync("", false, builder.Build()); + // Notify user that they were unmuted + builder = new EmbedBuilder + { + Title = "🔊 Unmute Notification", + Description = $"Your Mute on {Context.Guild.Name} has expired", + Color = ADMIN_COLOR + }; + + await badUser.SendMessageAsync(string.Empty, false, builder.Build()); } catch (HttpException) { @@ -796,18 +889,19 @@ public async Task UnmuteUser([Summary("user")]string user) public async Task ChannelLock() { try { - IGuildChannel textChannel = (IGuildChannel)Context.Channel; - EmbedBuilder builder = new EmbedBuilder { + var textChannel = (IGuildChannel)Context.Channel; + var builder = new EmbedBuilder { Description = $"🔒 <#{textChannel.Id}> Locked", Color = Color.Orange, - }; + foreach (IRole role in Context.Guild.Roles.Where(r => !r.Permissions.ManageMessages)) { var perms = textChannel.GetPermissionOverwrite(role).GetValueOrDefault(); await textChannel.AddPermissionOverwriteAsync(role, perms.Modify(sendMessages: PermValue.Deny)); } - await Context.Channel.SendMessageAsync("", false, builder.Build()); + + await Context.Channel.SendMessageAsync(string.Empty, false, builder.Build()); } catch { await Context.Channel.SendMessageAsync("Something went wrong!"); @@ -821,72 +915,88 @@ public async Task ChannelLock() public async Task ChannelUnLock() { try { - IGuildChannel textChannel = (IGuildChannel)Context.Channel; - EmbedBuilder builder = new EmbedBuilder { + var textChannel = (IGuildChannel) Context.Channel; + var builder = new EmbedBuilder { Description = $"🔓 <#{textChannel.Id}> Unlocked", Color = Color.DarkGreen, - }; + foreach (IRole role in Context.Guild.Roles.Where(r => !r.Permissions.ManageMessages)) { var perms = textChannel.GetPermissionOverwrite(role).GetValueOrDefault(); + if (role.Name != "nadeko-mute" && role.Name != "Muted") await textChannel.AddPermissionOverwriteAsync(role, perms.Modify(sendMessages: PermValue.Allow)); } - await Context.Channel.SendMessageAsync("", false, builder.Build()); + + await Context.Channel.SendMessageAsync(string.Empty, false, builder.Build()); } catch { await Context.Channel.SendMessageAsync("Something went wrong!"); } } - private IUser resolveUser(string input) + + private IUser ResolveUser(string input) { IUser user = null; - //resolve userID or @mention + + // Resolve userID or @mention if (Regex.IsMatch(input, @"\d{16,}")) { - string userID = Regex.Match(input, @"\d{16,}").Value; - user = Context.Client.GetUser(Convert.ToUInt64(userID)); + var userId = Regex.Match(input, @"\d{16,}").Value; + + user = Context.Client.GetUser(Convert.ToUInt64(userId)); } - //resolve username#0000 + // Resolve username#0000 else if (Regex.IsMatch(input, ".*#[0-9]{4}")) { - string[] splilt = input.Split("#"); - user = Context.Client.GetUser(splilt[0], splilt[1]); + var split = input.Split("#"); + + user = Context.Client.GetUser(split[0], split[1]); } + return user; } + private Embed CreateDescriptionEmbed(string description) { - EmbedBuilder builder = new EmbedBuilder + var builder = new EmbedBuilder { Description = description, Color = ADMIN_COLOR }; + return builder.Build(); } private async Task SendEmbed(Embed embed) { - await Context.Channel.SendMessageAsync("", false, embed); + await Context.Channel.SendMessageAsync(string.Empty, false, embed); } + private async Task UpdateForgivenStatus(string function, string type, string badUser) { IQueryable warnings = null; - bool oldStatus = (function == "forgiven") ? false : true; // if we are forgiving them then their old status must be unforgiven - if (string.IsNullOrEmpty(type) || string.IsNullOrEmpty(badUser) || (!type.ToLower().Equals("warning") && !type.ToLower().Equals("usernote"))) // invalid parameters + + var oldStatus = function != "forgiven"; // If we are forgiving them then their old status must be unforgiven + + if (string.IsNullOrEmpty(type) || string.IsNullOrEmpty(badUser) || (!type.ToLower().Equals("warning") && !type.ToLower().Equals("usernote"))) // Invalid parameters { - Embed embed = CreateDescriptionEmbed($"💾 Usage: `{(function == "forgiven" ? "forgive" : "unforgive")} [warning/usernote] [user]`"); + var embed = CreateDescriptionEmbed($"💾 Usage: `{(function == "forgiven" ? "forgive" : "unforgive")} [warning/usernote] [user]`"); + await SendEmbed(embed); + return; } - IUser user = resolveUser(badUser); + + var user = ResolveUser(badUser); - ulong uID; + ulong uId; + if (user != null) { - uID = user.Id; + uId = user.Id; } else if (Regex.IsMatch(badUser, @"\d{16,}")) // not in server but valid user id { - uID = Convert.ToUInt64(badUser); + uId = Convert.ToUInt64(badUser); } else // user not in server AND not valid user id { @@ -894,80 +1004,96 @@ private async Task UpdateForgivenStatus(string function, string type, string bad return; } - if (type == "warning") // forgive warning + switch (type) { - if (!_floofDB.Warnings.AsQueryable().Where(w => w.UserId == uID && w.GuildId == Context.Guild.Id && w.Forgiven == oldStatus).Any()) // there are no warnings for this user in this guild - { + // Forgive warning + // There are no warnings for this user in this guild + case "warning" when !_floofDb.Warnings.AsQueryable().Any(w => w.UserId == uId && w.GuildId == Context.Guild.Id && w.Forgiven == oldStatus): await Context.Channel.SendMessageAsync($"User has no warnings to be {function}!"); return; - } - warnings = _floofDB.Warnings.AsQueryable() - .Where(u => u.UserId == uID && u.GuildId == Context.Guild.Id && u.Forgiven == oldStatus) - .OrderByDescending(x => x.DateAdded).Take(10); - } - else if (type == "usernote") // forgive usernote - { - if (!_floofDB.UserNotes.AsQueryable().Where(w => w.UserId == uID && w.GuildId == Context.Guild.Id && w.Forgiven == oldStatus).Any()) // there are no user notes for this user in this guild - { + + case "warning": + warnings = _floofDb.Warnings.AsQueryable() + .Where(u => u.UserId == uId && u.GuildId == Context.Guild.Id && u.Forgiven == oldStatus) + .OrderByDescending(x => x.DateAdded).Take(10); + break; + + // Forgive usernote + // There are no user notes for this user in this guild + case "usernote" when !_floofDb.UserNotes.AsQueryable().Any(w => w.UserId == uId && w.GuildId == Context.Guild.Id && w.Forgiven == oldStatus): await Context.Channel.SendMessageAsync($"User has no user notes to be {function}!"); return; - } - warnings = _floofDB.UserNotes.AsQueryable() - .Where(u => u.UserId == uID && u.GuildId == Context.Guild.Id && u.Forgiven == oldStatus) - .OrderByDescending(x => x.DateAdded).Take(10); + + case "usernote": + warnings = _floofDb.UserNotes.AsQueryable() + .Where(u => u.UserId == uId && u.GuildId == Context.Guild.Id && u.Forgiven == oldStatus) + .OrderByDescending(x => x.DateAdded).Take(10); + break; } - EmbedBuilder builder = new EmbedBuilder(); - builder.Color = ADMIN_COLOR; - if (user == null) // no user, just id in database - builder.WithTitle($"{((type == "warnings") ? "Warnings" : "User Notes")} for {badUser}"); - else - builder.WithTitle($"{((type == "warnings") ? "Warnings" : "User Notes")} for {user.Username}#{user.Discriminator}"); - if (warnings == null) // for some reason didnt recieve data from database + var builder = new EmbedBuilder + { + Color = ADMIN_COLOR + }; + + builder.WithTitle(user == null + ? $"{((type == "warnings") ? "Warnings" : "User Notes")} for {badUser}" + : $"{((type == "warnings") ? "Warnings" : "User Notes")} for {user.Username}#{user.Discriminator}"); + + if (warnings == null) // For some reason didnt recieve data from database { Log.Error("Fatal error when trying to access warnings for the forgive user command!"); return; } - if (type == "warning") - { - foreach (Warning w in warnings) - { - builder.AddField($"**ID: {w.Id}** - {w.DateAdded.ToString("yyyy MMMM dd")} - {w.Moderator}", $"```{w.Reason}```"); - } - } - else if (type == "usernote") + + switch (type) { - foreach (UserNote w in warnings) - { - builder.AddField($"**ID: {w.Id}** - {w.DateAdded.ToString("yyyy MMMM dd")} - {w.Moderator}", $"```{w.Reason}```"); - } + case "warning": + foreach (Warning w in warnings) + { + builder.AddField($"**ID: {w.Id}** - {w.DateAdded:yyyy MMMM dd} - {w.Moderator}", $"```{w.Reason}```"); + } + + break; + + case "usernote": + foreach (UserNote w in warnings) + { + builder.AddField($"**ID: {w.Id}** - {w.DateAdded:yyyy MMMM dd} - {w.Moderator}", $"```{w.Reason}```"); + } + + break; } + await SendEmbed(builder.Build()); + try { await ReplyAsync($"Which would you like to be {function}? Please specify the ID."); - SocketMessage response = await NextMessageAsync(true, true, TimeSpan.FromSeconds(10)); // wait for reply from source user in source channel for 10 seconds + var response = await NextMessageAsync(true, true, TimeSpan.FromSeconds(10)); // Wait for reply from source user in source channel for 10 seconds + if (response == null) { await Context.Channel.SendMessageAsync("You did not respond in time. Aborting..."); + return; } - ulong warningId; - if (ulong.TryParse(response.Content, out warningId)) // response is of type integer + if (ulong.TryParse(response.Content, out var warningId)) // response is of type integer { if (type == "warning") { - foreach (Warning w in warnings) { if (w.Id == warningId) { var modId = Context.Message.Author.Id; var modUsername = $"{Context.Message.Author.Username}#{Context.Message.Author.Discriminator}"; + await SetWarningForgivenStatus(w, !oldStatus, modId); await Context.Channel.SendMessageAsync($"Got it! {modUsername} has {function} the warning with the ID {w.Id} and the reason: {w.Reason}."); + return; } } @@ -980,76 +1106,83 @@ private async Task UpdateForgivenStatus(string function, string type, string bad { var modId = Context.Message.Author.Id; var modUsername = $"{Context.Message.Author.Username}#{Context.Message.Author.Discriminator}"; + await SetUserNoteForgivenStatus(un, !oldStatus, modId); await Context.Channel.SendMessageAsync($"Got it! {modUsername} has {function} the user note with the ID {un.Id} and the reason: {un.Reason}."); + return; } } } + await Context.Channel.SendMessageAsync("You have provided either an incorrect response, or that warning ID is not in the list of warnings. Aborting..."); + return; } - else - { - await Context.Channel.SendMessageAsync("Invalid input, please provide a valid number. Aborting.."); - return; - } + + await Context.Channel.SendMessageAsync("Invalid input, please provide a valid number. Aborting.."); } - catch (Exception ex) + catch (Exception e) { - await Context.Channel.SendMessageAsync(ex.ToString()); + await Context.Channel.SendMessageAsync(e.ToString()); } } - private async Task SetWarningForgivenStatus(Warning w, bool status, ulong forgivenBy) + + private async Task SetWarningForgivenStatus(Warning warning, bool status, ulong forgivenBy) { - w.Forgiven = status; - w.ForgivenBy = forgivenBy; - await _floofDB.SaveChangesAsync(); + warning.Forgiven = status; + warning.ForgivenBy = forgivenBy; + + await _floofDb.SaveChangesAsync(); } - private async Task SetUserNoteForgivenStatus(UserNote un, bool status, ulong forgivenBy) + + private async Task SetUserNoteForgivenStatus(UserNote userNote, bool status, ulong forgivenBy) { - un.Forgiven = status; - un.ForgivenBy = forgivenBy; - await _floofDB.SaveChangesAsync(); + userNote.Forgiven = status; + userNote.ForgivenBy = forgivenBy; + + await _floofDb.SaveChangesAsync(); } + private Embed GetWarnings(ulong uid, bool isOwnLog) { - IQueryable formalWarnings = null; + IQueryable formalWarnings; IQueryable userNotes = null; - SocketUser badUser = Context.Client.GetUser(uid); + + var badUser = Context.Client.GetUser(uid); if (isOwnLog) { - formalWarnings = _floofDB.Warnings.AsQueryable() + formalWarnings = _floofDb.Warnings.AsQueryable() .Where(u => u.UserId == uid && u.GuildId == Context.Guild.Id && u.Forgiven == false) .OrderByDescending(x => x.DateAdded).Take(10); } - else // user notes are for mod view only + else // User notes are for mod view only { - - formalWarnings = _floofDB.Warnings.AsQueryable() + formalWarnings = _floofDb.Warnings.AsQueryable() .Where(u => u.UserId == uid && u.GuildId == Context.Guild.Id) .OrderByDescending(x => x.DateAdded).Take(10); - userNotes = _floofDB.UserNotes.AsQueryable() + userNotes = _floofDb.UserNotes.AsQueryable() .Where(u => u.UserId == uid && u.GuildId == Context.Guild.Id) .OrderByDescending(x => x.DateAdded).Take(10); } - if (!isOwnLog) // mod viewing someones history + if (!isOwnLog) // Mod viewing someones history { - if (badUser == null) // client cant get user - no mutual servers? + if (badUser == null) // Client cant get user - no mutual servers? { - if (formalWarnings.Count() == 0 && userNotes.Count() == 0) + if (!formalWarnings.Any() && !userNotes.Any()) { - string message = $"{uid} is a good noodle. They have no warnings or user notes!"; + var message = $"{uid} is a good noodle. They have no warnings or user notes!"; var embed = CreateDescriptionEmbed(message); + return embed; } } else { - if (formalWarnings.Count() == 0 && userNotes.Count() == 0) + if (!formalWarnings.Any() && !userNotes.Any()) { string message = $"{badUser.Username}#{badUser.Discriminator} is a good noodle. They have no warnings or user notes!"; var embed = CreateDescriptionEmbed(message); @@ -1057,9 +1190,9 @@ private Embed GetWarnings(ulong uid, bool isOwnLog) } } } - else // own users history + else // Own users history { - if (formalWarnings.Count() == 0) + if (!formalWarnings.Any()) { string message = $"You are a good noodle. You have no warnings!"; var embed = CreateDescriptionEmbed(message); @@ -1067,12 +1200,15 @@ private Embed GetWarnings(ulong uid, bool isOwnLog) } } - EmbedBuilder builder = new EmbedBuilder(); - builder.Color = ADMIN_COLOR; - int warningCount = 0; - int userNoteCount = 0; + var builder = new EmbedBuilder + { + Color = ADMIN_COLOR + }; + + var warningCount = 0; + var userNoteCount = 0; - if (badUser == null && !isOwnLog) // no user, just id in database + if (badUser == null && !isOwnLog) // No user, just id in database builder.WithTitle($"Warnings for {uid}"); else if (badUser != null && !isOwnLog) builder.WithTitle($"Warnings for {badUser.Username}#{badUser.Discriminator}"); @@ -1081,24 +1217,29 @@ private Embed GetWarnings(ulong uid, bool isOwnLog) if (!isOwnLog) { - if (formalWarnings.Count() != 0) // they have warnings + if (formalWarnings.Count() != 0) // They have warnings { builder.AddField(":warning: | Formal Warnings:", "\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_"); - foreach (Warning warning in formalWarnings) + + foreach (var warning in formalWarnings) { - var hyperLink = ""; + var hyperLink = String.Empty; + if (warning.warningUrl != null && Uri.IsWellFormedUriString(warning.warningUrl, UriKind.Absolute)) // make sure url is good hyperLink = $"[Jump To Warning]({warning.warningUrl})\n"; if (warning.Forgiven) { - IUser forgivenBy = resolveUser(warning.ForgivenBy.ToString()); - var forgivenByText = (forgivenBy == null) ? "" : $"(forgiven by {forgivenBy.Username}#{forgivenBy.Discriminator})"; - builder.AddField($"~~**{warningCount + 1}**. {warning.DateAdded.ToString("yyyy MMMM dd")} - {warning.Moderator}~~ {forgivenByText}", $"{hyperLink}```{warning.Reason}```"); + var forgivenBy = ResolveUser(warning.ForgivenBy.ToString()); + var forgivenByText = (forgivenBy == null) + ? string.Empty + : $"(forgiven by {forgivenBy.Username}#{forgivenBy.Discriminator})"; + + builder.AddField($"~~**{warningCount + 1}**. {warning.DateAdded:yyyy MMMM dd} - {warning.Moderator}~~ {forgivenByText}", $"{hyperLink}```{warning.Reason}```"); } else { - builder.AddField($"**{warningCount + 1}**. {warning.DateAdded.ToString("yyyy MMMM dd")} - {warning.Moderator}", $"{hyperLink}```{warning.Reason}```"); + builder.AddField($"**{warningCount + 1}**. {warning.DateAdded:yyyy MMMM dd} - {warning.Moderator}", $"{hyperLink}```{warning.Reason}```"); } warningCount++; } @@ -1107,42 +1248,46 @@ private Embed GetWarnings(ulong uid, bool isOwnLog) else { builder.AddField(":warning: | Formal Warnings:", "\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_"); - foreach (Warning warning in formalWarnings) + + foreach (var warning in formalWarnings) { - if (warning.Forgiven) // user doesnt need to see forgiven warnings + if (warning.Forgiven) // User doesnt need to see forgiven warnings { continue; } - else - { - builder.AddField($"**{warningCount + 1}**. {warning.DateAdded.ToString("yyyy MMMM dd")}", $"```{warning.Reason}```"); - } + + builder.AddField($"**{warningCount + 1}**. {warning.DateAdded:yyyy MMMM dd}", $"```{warning.Reason}```"); warningCount++; } } - if (!isOwnLog) + + if (isOwnLog) + return builder.Build(); + + if (!userNotes.Any()) + return builder.Build(); + + builder.AddField("\u200B", "\u200B"); // blank line + builder.AddField(":pencil: | User Notes:", "\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_"); + + foreach (var usernote in userNotes) { - if (userNotes.Count() != 0) // they have user notes + if (usernote.Forgiven) { - builder.AddField("\u200B", "\u200B"); // blank line - builder.AddField(":pencil: | User Notes:", "\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_"); - foreach (UserNote usernote in userNotes) - { - if (usernote.Forgiven) - { - IUser forgivenBy = resolveUser(usernote.ForgivenBy.ToString()); - var forgivenByText = (forgivenBy == null) ? "" : $"(forgiven by {forgivenBy.Username}#{forgivenBy.Discriminator})"; - builder.AddField($"~~**{userNoteCount + 1}**. {usernote.DateAdded.ToString("yyyy MMMM dd")} - {usernote.Moderator}~~ {forgivenByText}", $"```{usernote.Reason}```"); - - } - else - { - builder.AddField($"**{userNoteCount + 1}**. {usernote.DateAdded.ToString("yyyy MMMM dd")} - {usernote.Moderator}", $"```{usernote.Reason}```"); - userNoteCount++; - } - } + var forgivenBy = ResolveUser(usernote.ForgivenBy.ToString()); + var forgivenByText = (forgivenBy == null) + ? string.Empty + : $"(forgiven by {forgivenBy.Username}#{forgivenBy.Discriminator})"; + + builder.AddField($"~~**{userNoteCount + 1}**. {usernote.DateAdded:yyyy MMMM dd} - {usernote.Moderator}~~ {forgivenByText}", $"```{usernote.Reason}```"); + } + else + { + builder.AddField($"**{userNoteCount + 1}**. {usernote.DateAdded:yyyy MMMM dd} - {usernote.Moderator}", $"```{usernote.Reason}```"); + userNoteCount++; } } + return builder.Build(); } } diff --git a/Floofbot/Modules/ErrorLoggingCommands.cs b/Floofbot/Modules/ErrorLoggingCommands.cs index c8143dc1..f901e316 100644 --- a/Floofbot/Modules/ErrorLoggingCommands.cs +++ b/Floofbot/Modules/ErrorLoggingCommands.cs @@ -1,73 +1,85 @@ -using Discord; +using System.Threading.Tasks; +using Discord; using Discord.Addons.Interactive; using Discord.Commands; using Floofbot.Services.Repository; using Floofbot.Services.Repository.Models; -using System.Threading.Tasks; -[Summary("Error logging configuration commands")] -[Name("Error Logging Configuration Commands")] -[RequireUserPermission(GuildPermission.Administrator)] -[Group("errorloggingconfig")] -public class ErrorLoggingCommands : InteractiveBase +namespace Floofbot.Modules { - private FloofDataContext _floofDB; - - public ErrorLoggingCommands(FloofDataContext floofDB) + [Summary("Error logging configuration commands")] + [Name("Error Logging Configuration Commands")] + [RequireUserPermission(GuildPermission.Administrator)] + [Group("errorloggingconfig")] + public class ErrorLoggingCommands : InteractiveBase { - _floofDB = floofDB; - } + private FloofDataContext _floofDB; - private ErrorLogging GetServerConfig(ulong server) - { - // checks if server exists in database and adds if not - var serverConfig = _floofDB.ErrorLoggingConfigs.Find(server); - if (serverConfig == null) + public ErrorLoggingCommands(FloofDataContext floofDB) { - _floofDB.Add(new ErrorLogging - { - ServerId = server, - ChannelId = null, - IsOn = false - }); - _floofDB.SaveChanges(); - return _floofDB.ErrorLoggingConfigs.Find(server); + _floofDB = floofDB; } - else + + private ErrorLogging GetServerConfig(ulong server) { + // Checks if server exists in the database and adds if is not + var serverConfig = _floofDB.ErrorLoggingConfigs.Find(server); + + if (serverConfig == null) + { + _floofDB.Add(new ErrorLogging + { + ServerId = server, + ChannelId = null, + IsOn = false + }); + + _floofDB.SaveChanges(); + + return _floofDB.ErrorLoggingConfigs.Find(server); + } + return serverConfig; } - } - [Command("channel")] - [Summary("Sets the channel for logging fatal errors")] - public async Task Channel([Summary("Channel (eg #errors)")]Discord.IChannel channel = null) - { - if (channel == null) + [Command("channel")] + [Summary("Sets the channel for logging fatal errors")] + public async Task Channel([Summary("Channel (eg #errors)")]Discord.IChannel channel = null) { - channel = (IChannel)Context.Channel; + if (channel == null) + { + channel = (IChannel)Context.Channel; + } + + var serverConfig = GetServerConfig(Context.Guild.Id); + + serverConfig.ChannelId = channel.Id; + + await _floofDB.SaveChangesAsync(); + + await Context.Channel.SendMessageAsync("Channel updated! I will send fatal errors to <#" + channel.Id + ">"); } - var ServerConfig = GetServerConfig(Context.Guild.Id); - ServerConfig.ChannelId = channel.Id; - _floofDB.SaveChanges(); - await Context.Channel.SendMessageAsync("Channel updated! I will send fatal errors to <#" + channel.Id + ">"); - } - [Command("toggle")] - [Summary("Toggles error logging")] - public async Task Toggle() - { - - // try toggling - // check the status of logger - var ServerConfig = GetServerConfig(Context.Guild.Id); - if (ServerConfig.ChannelId == null) + [Command("toggle")] + [Summary("Toggles error logging")] + public async Task Toggle() + { + // Try toggling + // Check the status of logger + var serverConfig = GetServerConfig(Context.Guild.Id); + + if (serverConfig.ChannelId == null) { await Context.Channel.SendMessageAsync("Channel not set! Please set the channel before toggling error logging."); + return; } - ServerConfig.IsOn = !ServerConfig.IsOn; - _floofDB.SaveChanges(); - await Context.Channel.SendMessageAsync("Error Logging " + (ServerConfig.IsOn ? "Enabled!" : "Disabled!")); + + serverConfig.IsOn = !serverConfig.IsOn; + + await _floofDB.SaveChangesAsync(); + + await Context.Channel.SendMessageAsync("Error Logging " + (serverConfig.IsOn ? "Enabled!" : "Disabled!")); + } } } \ No newline at end of file diff --git a/Floofbot/Modules/Filter.cs b/Floofbot/Modules/Filter.cs index bf5efa06..0b6f1f3c 100644 --- a/Floofbot/Modules/Filter.cs +++ b/Floofbot/Modules/Filter.cs @@ -2,7 +2,6 @@ using Discord; using System.Threading.Tasks; using Discord.Commands; -using System.Text.RegularExpressions; using Floofbot.Services.Repository; using Floofbot.Services.Repository.Models; using System.Linq; @@ -20,17 +19,20 @@ namespace Floofbot.Modules [RequireUserPermission(GuildPermission.BanMembers)] public class Filter : InteractiveBase { - private static readonly Discord.Color EMBED_COLOR = Color.Magenta; + private static readonly Color EMBED_COLOR = Color.Magenta; private static readonly int WORDS_PER_PAGE = 50; private FloofDataContext _floofDb; + public Filter(FloofDataContext floofDb) { _floofDb = floofDb; } - private void CheckServerEntryExists(ulong server) + + private async Task CheckServerEntryExists(ulong server) { - // checks if server exists in database and adds if not + // Checks if server exists in database and adds if is not var serverConfig = _floofDb.FilterConfigs.Find(server); + if (serverConfig == null) { _floofDb.Add(new FilterConfig @@ -38,13 +40,16 @@ private void CheckServerEntryExists(ulong server) ServerId = server, IsOn = false }); - _floofDb.SaveChanges(); + + await _floofDb.SaveChangesAsync(); } } + private bool CheckWordEntryExists(string word, SocketGuild guild) { - // checks if a word exists in the filter db - bool wordEntry = _floofDb.FilteredWords.AsQueryable().Where(w => w.ServerId == guild.Id).Where(w => w.Word == word).Any(); + // Checks if a word exists in the filter db + var wordEntry = _floofDb.FilteredWords.AsQueryable().Where(w => w.ServerId == guild.Id).Any(w => w.Word == word); + return wordEntry; } @@ -54,32 +59,36 @@ public async Task Toggle([Summary("Either 'channel' or 'server'")] string toggle { if (toggleType == "server") { - // try toggling + // Try toggling try { - CheckServerEntryExists(Context.Guild.Id); - // check the status of server filtering - var ServerConfig = _floofDb.FilterConfigs.Find(Context.Guild.Id); - ServerConfig.IsOn = !ServerConfig.IsOn; - _floofDb.SaveChanges(); - await Context.Channel.SendMessageAsync("Server Filtering " + (ServerConfig.IsOn ? "Enabled!" : "Disabled!")); + await CheckServerEntryExists(Context.Guild.Id); + // Check the status of server filtering + var serverConfig = _floofDb.FilterConfigs.Find(Context.Guild.Id); + + serverConfig.IsOn = !serverConfig.IsOn; + + await _floofDb.SaveChangesAsync(); + + await Context.Channel.SendMessageAsync("Server Filtering " + (serverConfig.IsOn ? "Enabled!" : "Disabled!")); } - catch (Exception ex) + catch (Exception e) { - await Context.Channel.SendMessageAsync("An error occured: " + ex.Message); - Serilog.Log.Error("Error when trying to toggle the server filtering: " + ex); - return; + await Context.Channel.SendMessageAsync("An error occured: " + e.Message); + + Serilog.Log.Error("Error when trying to toggle the server filtering: " + e); } } else if (toggleType == "channel") { - // try toggling + // Try toggling try { - CheckServerEntryExists(Context.Guild.Id); - // check the status of logger - var channelData = _floofDb.FilterChannelWhitelists.Find(Context.Channel.Id); - bool channelInDatabase = false; + await CheckServerEntryExists(Context.Guild.Id); + + // Check the status of logger + var channelData = await _floofDb.FilterChannelWhitelists.FindAsync(Context.Channel.Id); + bool channelInDatabase; if (channelData == null) { @@ -88,27 +97,32 @@ public async Task Toggle([Summary("Either 'channel' or 'server'")] string toggle ChannelId = Context.Channel.Id, ServerId = Context.Guild.Id }); - _floofDb.SaveChanges(); + + await _floofDb.SaveChangesAsync(); + channelInDatabase = true; } else { _floofDb.FilterChannelWhitelists.Remove(channelData); - _floofDb.SaveChanges(); + + await _floofDb.SaveChangesAsync(); + channelInDatabase = false; } + await Context.Channel.SendMessageAsync("Filtering For This Channel Is " + (!channelInDatabase ? "Enabled!" : "Disabled!")); } - catch (Exception ex) + catch (Exception e) { - await Context.Channel.SendMessageAsync("An error occured: " + ex.Message); - Serilog.Log.Error("Error when trying to toggle the channel filtering: " + ex); - return; + await Context.Channel.SendMessageAsync("An error occured: " + e.Message); + + Serilog.Log.Error("Error when trying to toggle the channel filtering: " + e); } } else { - await Context.Channel.SendMessageAsync("", false, new EmbedBuilder { Description = $"💾 Usage: `filter toggle channel/server`", Color = EMBED_COLOR }.Build()); + await Context.Channel.SendMessageAsync(string.Empty, false, new EmbedBuilder { Description = $"💾 Usage: `filter toggle channel/server`", Color = EMBED_COLOR }.Build()); } } @@ -116,68 +130,75 @@ public async Task Toggle([Summary("Either 'channel' or 'server'")] string toggle [Command("add")] public async Task AddFilteredWord([Summary("filtered word")][Remainder] string word) { - CheckServerEntryExists(Context.Guild.Id); - string newWord = word.ToLower(); - bool wordAlreadyExists = CheckWordEntryExists(newWord, Context.Guild); + await CheckServerEntryExists(Context.Guild.Id); + + var newWord = word.ToLower(); + var wordAlreadyExists = CheckWordEntryExists(newWord, Context.Guild); if (wordAlreadyExists) { await Context.Channel.SendMessageAsync($"{word} is already being filtered!"); return; } - else + + try { - try - { - _floofDb.Add(new FilteredWord - { - Word = newWord, - ServerId = Context.Guild.Id - }); - _floofDb.SaveChanges(); - await Context.Channel.SendMessageAsync($"{word} is now being filtered!"); - } - catch (Exception ex) + _floofDb.Add(new FilteredWord { - await Context.Channel.SendMessageAsync("An error occured: " + ex.Message); - Serilog.Log.Error("Error when trying to add a filtered word: " + ex); - } + Word = newWord, + ServerId = Context.Guild.Id + }); + + await _floofDb.SaveChangesAsync(); + + await Context.Channel.SendMessageAsync($"{word} is now being filtered!"); + } + catch (Exception e) + { + await Context.Channel.SendMessageAsync("An error occured: " + e.Message); + + Serilog.Log.Error("Error when trying to add a filtered word: " + e); } } + [Summary("Removed an existing filtered word")] [Command("remove")] public async Task RemoveFilteredWord([Summary("filtered word")][Remainder] string word) { - CheckServerEntryExists(Context.Guild.Id); - string oldWord = word.ToLower(); - bool wordAlreadyExists = CheckWordEntryExists(oldWord, Context.Guild); + await CheckServerEntryExists(Context.Guild.Id); + + var oldWord = word.ToLower(); + var wordAlreadyExists = CheckWordEntryExists(oldWord, Context.Guild); if (!wordAlreadyExists) { await Context.Channel.SendMessageAsync($"{word} isn't being filtered!"); return; } - else + + try { - try - { - FilteredWord wordEntry = _floofDb.FilteredWords.AsQueryable().Where(w => w.ServerId == Context.Guild.Id).Where(w => w.Word == oldWord).First(); - _floofDb.Remove(wordEntry); - _floofDb.SaveChanges(); - await Context.Channel.SendMessageAsync($"{word} is no longer being filtered!"); - } - catch (Exception ex) - { - await Context.Channel.SendMessageAsync("An error occured: " + ex.Message); - Serilog.Log.Error("Error when trying to toggle the channel filtering: " + ex); - } + var wordEntry = _floofDb.FilteredWords.AsQueryable().Where(w => w.ServerId == Context.Guild.Id).First(w => w.Word == oldWord); + + _floofDb.Remove(wordEntry); + + await _floofDb.SaveChangesAsync(); + + await Context.Channel.SendMessageAsync($"{word} is no longer being filtered!"); + } + catch (Exception e) + { + await Context.Channel.SendMessageAsync("An error occured: " + e.Message); + + Serilog.Log.Error("Error when trying to toggle the channel filtering: " + e); } } + [Summary("Lists all filtered words")] [Command("list")] public async Task ListFilteredWords() { - List filteredWords = _floofDb.FilteredWords.AsQueryable() + var filteredWords = _floofDb.FilteredWords.AsQueryable() .Where(x => x.ServerId == Context.Guild.Id) .OrderBy(x => x.Id) .ToList(); @@ -185,29 +206,34 @@ public async Task ListFilteredWords() if (filteredWords.Count == 0) { await Context.Channel.SendMessageAsync("No words have been filtered yet"); + return; } - List pages = new List(); - int numPages = (int)Math.Ceiling((double)filteredWords.Count / WORDS_PER_PAGE); - int index; + var pages = new List(); + var numPages = (int)Math.Ceiling((double)filteredWords.Count / WORDS_PER_PAGE); + for (int i = 0; i < numPages; i++) { - string text = "```\n"; + var text = "```\n"; + for (int j = 0; j < WORDS_PER_PAGE; j++) { - index = i * WORDS_PER_PAGE + j; + var index = (i * WORDS_PER_PAGE) + j; + if (index < filteredWords.Count) { text += $"{index + 1}. {filteredWords[index].Word}\n"; } } + text += "\n```"; + pages.Add(new PaginatedMessage.Page { Description = text }); - }; + } var pager = new PaginatedMessage { @@ -218,6 +244,7 @@ public async Task ListFilteredWords() Options = PaginatedAppearanceOptions.Default, TimeStamp = DateTimeOffset.UtcNow }; + await PagedReplyAsync(pager, new ReactionList { Forward = true, diff --git a/Floofbot/Modules/Fun.cs b/Floofbot/Modules/Fun.cs index 1051cce9..e15bc168 100644 --- a/Floofbot/Modules/Fun.cs +++ b/Floofbot/Modules/Fun.cs @@ -12,18 +12,20 @@ namespace Floofbot.Modules [Name("Fun")] public class Fun : ModuleBase { - private static readonly Discord.Color EMBED_COLOR = Color.DarkOrange; - private static Random rand = new Random(); + private static readonly Color EMBED_COLOR = Color.DarkOrange; + private static Random _random = new Random(); [Command("8ball")] [Summary("Ask the Magic 8-Ball a question")] public async Task AskEightBall([Summary("question")][Remainder] string question) { - EmbedBuilder builder = new EmbedBuilder(); - builder.Title = "Magic 8 Ball"; - builder.AddField("Question", question); - builder.AddField("Answer", EightBall.GetRandomResponse()); - builder.Color = EMBED_COLOR; + var builder = new EmbedBuilder + { + Title = "Magic 8 Ball", + Color = EMBED_COLOR + }.AddField("Question", question) + .AddField("Answer", EightBall.GetRandomResponse()); + await SendEmbed(builder.Build()); } @@ -31,14 +33,15 @@ public async Task AskEightBall([Summary("question")][Remainder] string question) [Summary("Get an xkcd comic by ID. If no ID given, get the latest one.")] public async Task XKCD([Summary("Comic ID")] string comicId = "") { - int parsedComicId; - if ((!int.TryParse(comicId, out parsedComicId) || parsedComicId <= 0) && !String.IsNullOrEmpty(comicId)) + if ((!int.TryParse(comicId, out var parsedComicId) || parsedComicId <= 0) && !String.IsNullOrEmpty(comicId)) { await Context.Channel.SendMessageAsync("Comic ID must be a positive integer less than or equal to " + Int32.MaxValue + "."); + return; } - string json; + var json = string.Empty; + if (parsedComicId == 0) { json = await ApiFetcher.RequestSiteContentAsString("https://xkcd.com/info.0.json"); @@ -51,23 +54,28 @@ public async Task XKCD([Summary("Comic ID")] string comicId = "") if (string.IsNullOrEmpty(json)) { await Context.Channel.SendMessageAsync("404 Not Found"); + return; } + string imgLink; string imgHoverText; string comicTitle; - using (JsonDocument parsedJson = JsonDocument.Parse(json)) + + using (var parsedJson = JsonDocument.Parse(json)) { imgLink = parsedJson.RootElement.GetProperty("img").ToString(); imgHoverText = parsedJson.RootElement.GetProperty("alt").ToString(); comicTitle = parsedJson.RootElement.GetProperty("safe_title").ToString(); } - EmbedBuilder builder = new EmbedBuilder(); - builder.Title = comicTitle; - builder.WithImageUrl(imgLink); - builder.WithFooter(imgHoverText); - builder.Color = EMBED_COLOR; + var builder = new EmbedBuilder + { + Title = comicTitle, + Color = EMBED_COLOR + }.WithImageUrl(imgLink) + .WithFooter(imgHoverText); + await SendEmbed(builder.Build()); } @@ -77,7 +85,8 @@ public async Task RollDice([Summary("dice")] string diceStr = "") { try { - Dice dice = Dice.FromString(diceStr); + var dice = Dice.FromString(diceStr); + await Context.Channel.SendMessageAsync(string.Join(" ", dice.GenerateRolls())); } catch (ArgumentException e) @@ -93,7 +102,8 @@ public async Task RollDice([Summary("dice")] string diceStr = "") [Summary("Responds with a random cat fact")] public async Task RequestCatFact() { - string fact = await ApiFetcher.RequestStringFromApi("https://catfact.ninja/fact", "fact"); + var fact = await ApiFetcher.RequestStringFromApi("https://catfact.ninja/fact", "fact"); + if (!string.IsNullOrEmpty(fact)) { await Context.Channel.SendMessageAsync(fact); @@ -108,7 +118,8 @@ public async Task RequestCatFact() [Summary("Responds with a random fox fact")] public async Task RequestFoxFact() { - string fact = await ApiFetcher.RequestStringFromApi("https://some-random-api.ml/facts/fox", "fact"); + var fact = await ApiFetcher.RequestStringFromApi("https://some-random-api.ml/facts/fox", "fact"); + if (!string.IsNullOrEmpty(fact)) { await Context.Channel.SendMessageAsync(fact); @@ -123,7 +134,8 @@ public async Task RequestFoxFact() [Summary("Responds with a random cat")] public async Task RequestCat() { - string fileUrl = await ApiFetcher.RequestEmbeddableUrlFromApi("https://aws.random.cat/meow", "file"); + var fileUrl = await ApiFetcher.RequestEmbeddableUrlFromApi("https://aws.random.cat/meow", "file"); + if (!string.IsNullOrEmpty(fileUrl) && Uri.IsWellFormedUriString(fileUrl, UriKind.Absolute)) { await SendAnimalEmbed(":cat:", fileUrl); @@ -138,7 +150,8 @@ public async Task RequestCat() [Summary("Responds with a random dog")] public async Task RequestDog() { - string fileUrl = await ApiFetcher.RequestEmbeddableUrlFromApi("https://random.dog/woof.json", "url"); + var fileUrl = await ApiFetcher.RequestEmbeddableUrlFromApi("https://random.dog/woof.json", "url"); + if (!string.IsNullOrEmpty(fileUrl) && Uri.IsWellFormedUriString(fileUrl, UriKind.Absolute)) { await SendAnimalEmbed(":dog:", fileUrl); @@ -153,7 +166,8 @@ public async Task RequestDog() [Summary("Responds with a random fox")] public async Task RequestFox() { - string fileUrl = await ApiFetcher.RequestEmbeddableUrlFromApi("https://wohlsoft.ru/images/foxybot/randomfox.php", "file"); + var fileUrl = await ApiFetcher.RequestEmbeddableUrlFromApi("https://wohlsoft.ru/images/foxybot/randomfox.php", "file"); + if (!string.IsNullOrEmpty(fileUrl) && Uri.IsWellFormedUriString(fileUrl, UriKind.Absolute)) { await SendAnimalEmbed(":fox:", fileUrl); @@ -168,7 +182,8 @@ public async Task RequestFox() [Summary("Responds with a random birb")] public async Task RequestBirb() { - string fileUrl = await ApiFetcher.RequestEmbeddableUrlFromApi("https://random.birb.pw/tweet.json", "file"); + var fileUrl = await ApiFetcher.RequestEmbeddableUrlFromApi("https://random.birb.pw/tweet.json", "file"); + if (!string.IsNullOrEmpty(fileUrl) && Uri.IsWellFormedUriString(fileUrl, UriKind.Relative)) { fileUrl = "https://random.birb.pw/img/" + fileUrl; @@ -186,31 +201,38 @@ public async Task RequestBirb() { if (!string.IsNullOrEmpty(choices)) { - string[] splitChoices = choices.Split(";") + var splitChoices = choices.Split(";") .Select(choice => choice.Trim()) .Where(choice => !string.IsNullOrEmpty(choice)).ToArray(); + if (splitChoices.Length == 1) { await Context.Channel.SendMessageAsync("You need to give me more than one choice!"); + return; } - else if (splitChoices.Length != 0) + + if (splitChoices.Length != 0) { - EmbedBuilder choiceEmbed = new EmbedBuilder(){ - Description = "Chosen choice: " + splitChoices[rand.Next(splitChoices.Length)], + var choiceEmbed = new EmbedBuilder(){ + Description = "Chosen choice: " + splitChoices[_random.Next(splitChoices.Length)], Color = EMBED_COLOR }; - await Context.Channel.SendMessageAsync("", false, choiceEmbed.Build()); + await Context.Channel.SendMessageAsync(string.Empty, false, choiceEmbed.Build()); + return; } } - string usageString = "Not enough options were provided, or all options were whitespace.\n" + + + var usageString = "Not enough options were provided, or all options were whitespace.\n" + "Example usage: `.choice choiceA; choiceB; choiceC`"; - EmbedBuilder builder = new EmbedBuilder { + + var builder = new EmbedBuilder { Description = usageString, Color = EMBED_COLOR }; + await SendEmbed(builder.Build()); } @@ -232,27 +254,31 @@ public async Task Minesweeper([Summary("width")]int width, [Summary("height")]in } else { - MinesweeperBoard game = new MinesweeperBoard(height, width, bombs); - EmbedBuilder builder = new EmbedBuilder(); - builder.Title = ":bomb: Minesweeper"; - builder.Color = EMBED_COLOR; - builder.Description = game.ToString(); + var game = new MinesweeperBoard(height, width, bombs); + var builder = new EmbedBuilder + { + Title = ":bomb: Minesweeper", + Color = EMBED_COLOR, + Description = game.ToString() + }; + await SendEmbed(builder.Build()); } } private async Task SendAnimalEmbed(string title, string fileUrl) { - EmbedBuilder builder = new EmbedBuilder() + var builder = new EmbedBuilder() .WithTitle(title) .WithColor(EMBED_COLOR) .WithImageUrl(fileUrl); + await SendEmbed(builder.Build()); } private async Task SendEmbed(Embed embed) { - await Context.Channel.SendMessageAsync("", false, embed); + await Context.Channel.SendMessageAsync(string.Empty, false, embed); } } } diff --git a/Floofbot/Modules/Help.cs b/Floofbot/Modules/Help.cs index 6aee036a..84d9675b 100644 --- a/Floofbot/Modules/Help.cs +++ b/Floofbot/Modules/Help.cs @@ -26,57 +26,60 @@ public Help(CommandService commandService, IServiceProvider serviceProvider) [Command("help")] public async Task HelpCommand() { - List modules = _commandService.Modules.ToList(); + var modules = _commandService.Modules.ToList(); var moduleCommands = new Dictionary>(); foreach (ModuleInfo module in modules) { moduleCommands.Add(module.Name, new List()); } + foreach (CommandInfo command in _commandService.Commands) { moduleCommands[command.Module.Name].Add(command); } - List fields = new List(); - List pages = new List(); - foreach (ModuleInfo module in modules) + var fields = new List(); + var pages = new List(); + + foreach (var module in modules) { - foreach (CommandInfo command in moduleCommands[module.Name]) - { - if (!string.IsNullOrEmpty(command.Name)) // only show commands with names - { - var userMeetsCommandPreconditions = await command.CheckPreconditionsAsync(Context); - if (userMeetsCommandPreconditions.IsSuccess) - { - string aliases = ""; - var aliasesWithoutCommandName = command.Aliases.Where(x => !x.Contains(command.Name)); // remove the cmd name/group from aliases - - if (aliasesWithoutCommandName != null && aliasesWithoutCommandName.Any()) // is not null or empty - aliases = "(aliases: " + string.Join(", ", aliasesWithoutCommandName) + ")"; - - fields.Add(new EmbedFieldBuilder() - { - Name = $"{command.Name} {aliases}", - Value = command.Summary ?? "No command description available", - IsInline = false - }); - } - } - } - if (fields.Count > 0) // don't show empty pages + foreach (var command in moduleCommands[module.Name]) { - pages.Add(new PaginatedMessage.Page + if (string.IsNullOrEmpty(command.Name)) continue; + + var userMeetsCommandPreconditions = await command.CheckPreconditionsAsync(Context); + + if (!userMeetsCommandPreconditions.IsSuccess) continue; + + var aliases = string.Empty; + var aliasesWithoutCommandName = command.Aliases.Where(x => !x.Contains(command.Name)).ToArray(); // remove the cmd name/group from aliases + + if (aliasesWithoutCommandName != null && aliasesWithoutCommandName.Any()) // is not null or empty + aliases = "(aliases: " + string.Join(", ", aliasesWithoutCommandName) + ")"; + + fields.Add(new EmbedFieldBuilder() { - Author = new EmbedAuthorBuilder { Name = module.Name }, - Fields = new List(fields), - Description = module.Summary ?? "No module description available" + Name = $"{command.Name} {aliases}", + Value = command.Summary ?? "No command description available", + IsInline = false }); - fields.Clear(); } + + if (fields.Count <= 0) continue; + + pages.Add(new PaginatedMessage.Page + { + Author = new EmbedAuthorBuilder { Name = module.Name }, + Fields = new List(fields), + Description = module.Summary ?? "No module description available" + }); + + fields.Clear(); } - string message = Context.User.Mention; + var message = Context.User.Mention; + await PostHelpPages(message, pages); } @@ -85,48 +88,54 @@ public async Task HelpCommand() [Name("help ")] public async Task HelpCommand([Summary("module name")] string requestedModule) { - List moduleNames = _commandService.Modules.Select(x => x.Name.ToLower()).ToList(); + var moduleNames = _commandService.Modules.Select(x => x.Name.ToLower()).ToList(); + if (!moduleNames.Contains(requestedModule.ToLower())) { await Context.Channel.SendMessageAsync($"Unable to find commands available for '{requestedModule}'"); return; } - IEnumerable moduleCommands = _commandService.Commands + var moduleCommands = _commandService.Commands .Where(command => command.Module.Name.ToLower() == requestedModule.ToLower()); - List fields = new List(); - List pages = new List(); + var fields = new List(); + var pages = new List(); + foreach (var cmd in moduleCommands) { var userMeetsCommandPreconditions = await cmd.CheckPreconditionsAsync(Context); - if (userMeetsCommandPreconditions.IsSuccess) + + if (!userMeetsCommandPreconditions.IsSuccess) continue; + + foreach (ParameterInfo param in cmd.Parameters) { - foreach (ParameterInfo param in cmd.Parameters) + fields.Add(new EmbedFieldBuilder() { - fields.Add(new EmbedFieldBuilder() - { - Name = param.Name, - Value = param.Summary ?? "No parameter description available", - IsInline = false - }); - } - pages.Add(new PaginatedMessage.Page - { - Author = new EmbedAuthorBuilder { Name = cmd.Name }, - Fields = new List(fields), - Description = cmd.Summary ?? "No command description available" + Name = param.Name, + Value = param.Summary ?? "No parameter description available", + IsInline = false }); - fields.Clear(); } + + pages.Add(new PaginatedMessage.Page + { + Author = new EmbedAuthorBuilder { Name = cmd.Name }, + Fields = new List(fields), + Description = cmd.Summary ?? "No command description available" + }); + + fields.Clear(); } + if (pages.Count > 0) { - string message = $"{Context.User.Mention} here are the commands available for '{requestedModule}'!"; + var message = $"{Context.User.Mention} here are the commands available for '{requestedModule}'!"; + await PostHelpPages(message, pages); } - else // user doesnt meet preconditions to use any of the commands in the module - return; + // If we did not enter the if-statement: + // User doesnt meet preconditions to use any of the commands in the module } private async Task PostHelpPages(string message, List pages) @@ -141,6 +150,7 @@ private async Task PostHelpPages(string message, List pag Options = PaginatedAppearanceOptions.Default, TimeStamp = DateTimeOffset.UtcNow }; + await PagedReplyAsync(pager, new ReactionList { Forward = true, diff --git a/Floofbot/Modules/Helpers/ApiFetcher.cs b/Floofbot/Modules/Helpers/ApiFetcher.cs index e2e2144d..e637377a 100644 --- a/Floofbot/Modules/Helpers/ApiFetcher.cs +++ b/Floofbot/Modules/Helpers/ApiFetcher.cs @@ -10,7 +10,7 @@ namespace Floofbot.Modules.Helpers { class ApiFetcher { - private static HttpClient httpClient; + private static readonly HttpClient HTTP_CLIENT; private static readonly int MAX_SUPPORTED_EMBED_FETCH_ATTEMPTS = 1; private static readonly List SUPPORTED_EMBED_EXTENSIONS = new List { @@ -19,32 +19,37 @@ class ApiFetcher static ApiFetcher() { - httpClient = new HttpClient(); - httpClient.Timeout = new TimeSpan(0, 0, 0, 2); // 2 seconds + HTTP_CLIENT = new HttpClient(); + HTTP_CLIENT.Timeout = new TimeSpan(0, 0, 0, 2); // 2 seconds } public static async Task RequestEmbeddableUrlFromApi(string apiUrl, string key) { - string url; + var url = string.Empty; + for (int attempts = 0; attempts < MAX_SUPPORTED_EMBED_FETCH_ATTEMPTS; attempts++) { url = await RequestStringFromApi(apiUrl, key); + if (!string.IsNullOrEmpty(url) && SUPPORTED_EMBED_EXTENSIONS.Any(ext => url.EndsWith(ext))) { return url; } } + return string.Empty; } public static async Task RequestStringFromApi(string apiUrl, string key) { - string json = await RequestSiteContentAsString(apiUrl); + var json = await RequestSiteContentAsString(apiUrl); + if (string.IsNullOrEmpty(json)) { return string.Empty; } - using (JsonDocument jsonDocument = JsonDocument.Parse(json)) + + using (var jsonDocument = JsonDocument.Parse(json)) { try { @@ -53,6 +58,7 @@ public static async Task RequestStringFromApi(string apiUrl, string key) catch (Exception e) { Log.Error(e.Message); + return string.Empty; } } @@ -62,11 +68,12 @@ public static async Task RequestSiteContentAsString(string apiUrl) { try { - return await httpClient.GetStringAsync(apiUrl); + return await HTTP_CLIENT.GetStringAsync(apiUrl); } catch (Exception e) { Log.Error(e.Message); + return string.Empty; } } diff --git a/Floofbot/Modules/Helpers/Dice.cs b/Floofbot/Modules/Helpers/Dice.cs index b1f631f0..b678a5dc 100644 --- a/Floofbot/Modules/Helpers/Dice.cs +++ b/Floofbot/Modules/Helpers/Dice.cs @@ -8,8 +8,8 @@ class Dice { private static readonly int MAX_NUM_DICE = 20; private static readonly int MAX_NUM_SIDES = 1000; - private int _numDice; - private int _numSides; + private readonly int _numDice; + private readonly int _numSides; private Dice(int numDice, int numSides) { @@ -22,41 +22,49 @@ public static Dice FromString(string diceStr) var match = Regex.Match(diceStr, @"^(?\d+)?[dD](?\d+)$", RegexOptions.ExplicitCapture | RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.RightToLeft); - int numDice = 1; + var numDice = 1; int numSides; if (!match.Success) { throw new ArgumentException("Dice rolled must be in a format such as 1d20, or d5"); } - else if (!int.TryParse(match.Groups["numSides"].Value, out numSides) || numSides > MAX_NUM_SIDES) + + if (!int.TryParse(match.Groups["numSides"].Value, out numSides) || numSides > MAX_NUM_SIDES) { throw new ArgumentException($"Each dice can have at most {MAX_NUM_SIDES} sides."); } - else if (match.Groups["numDice"].Success && - (!int.TryParse(match.Groups["numDice"].Value, out numDice) || numDice > MAX_NUM_DICE)) + + if (match.Groups["numDice"].Success && + (!int.TryParse(match.Groups["numDice"].Value, out numDice) || numDice > MAX_NUM_DICE)) { throw new ArgumentException($"At most {MAX_NUM_DICE} dice can be rolled at once."); } - else if (numDice == 0) + + if (numDice == 0) { throw new ArgumentException($"At least one dice must be rolled."); } - else if (numSides == 0) + + if (numSides == 0) { throw new ArgumentException($"Each dice must have at least one side."); } + return new Dice(numDice, numSides); } public List GenerateRolls() { - Random random = new Random(); - List rolls = new List(_numDice); + var random = new Random(); + + var rolls = new List(_numDice); + for (int i = 0; i < _numDice; i++) { rolls.Add(random.Next(_numSides) + 1); } + return rolls; } } diff --git a/Floofbot/Modules/Helpers/EightBall.cs b/Floofbot/Modules/Helpers/EightBall.cs index f81c2a0f..1ba68bf4 100644 --- a/Floofbot/Modules/Helpers/EightBall.cs +++ b/Floofbot/Modules/Helpers/EightBall.cs @@ -30,8 +30,9 @@ public static class EightBall public static string GetRandomResponse() { - Random random = new Random(); - int randomNumber = random.Next(RESPONSES.Count); + var random = new Random(); + var randomNumber = random.Next(RESPONSES.Count); + return RESPONSES[randomNumber]; } } diff --git a/Floofbot/Modules/Helpers/MinesweeperBoard.cs b/Floofbot/Modules/Helpers/MinesweeperBoard.cs index 60876861..a928ebb8 100644 --- a/Floofbot/Modules/Helpers/MinesweeperBoard.cs +++ b/Floofbot/Modules/Helpers/MinesweeperBoard.cs @@ -6,47 +6,51 @@ namespace Floofbot.Modules.Helpers public class MinesweeperBoard { private static readonly string BOMB_SQUARE = "||:bomb:||"; - private string[,] grid; - private int height; - private int width; - private int bombCount; + private readonly string[,] _grid; + private readonly int _height; + private readonly int _width; + private readonly int _bombCount; public MinesweeperBoard(int height, int width, int bombCount) { - this.height = height; - this.width = width; - this.bombCount = bombCount; - grid = new string[this.height, this.width]; + _height = height; + _width = width; + _bombCount = bombCount; + _grid = new string[_height, _width]; + PlantBombs(); FillBoardNumbers(); } private void PlantBombs() { - Random rnd = new Random(); - for (int i = 0; i < bombCount; i++) + var rnd = new Random(); + + for (int i = 0; i < _bombCount; i++) { int randX; int randY; + do { - randX = rnd.Next(height); - randY = rnd.Next(width); + randX = rnd.Next(_height); + randY = rnd.Next(_width); } - while (grid[randX, randY] == BOMB_SQUARE); - grid[randX, randY] = BOMB_SQUARE; + while (_grid[randX, randY] == BOMB_SQUARE); + + _grid[randX, randY] = BOMB_SQUARE; } } private void FillBoardNumbers() { - for (int i = 0; i < this.height; i++) + for (int i = 0; i < _height; i++) { - for (int j = 0; j < this.width; j++) + for (int j = 0; j < _width; j++) { - if (grid[i, j] != BOMB_SQUARE) + if (_grid[i, j] != BOMB_SQUARE) { - grid[i, j] = CountNeighbouringBombs(i, j).ToString(); + _grid[i, j] = CountNeighbouringBombs(i, j).ToString(); } } } @@ -54,26 +58,29 @@ private void FillBoardNumbers() private int CountNeighbouringBombs(int x, int y) { - int count = 0; + var count = 0; + for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { if (!(i == 0 && j == 0) - && x + i >= 0 && x + i < grid.GetLength(0) - && y + j >= 0 && y + j < grid.GetLength(1) - && grid[x + i, y + j] == BOMB_SQUARE) + && x + i >= 0 && x + i < _grid.GetLength(0) + && y + j >= 0 && y + j < _grid.GetLength(1) + && _grid[x + i, y + j] == BOMB_SQUARE) { count++; } } } + return count; } public override string ToString() { - string board = ""; + var board = string.Empty; + var reponse = new Dictionary { { "0" ,"||:zero:||" }, { "1" ,"||:one:||" }, @@ -86,17 +93,18 @@ public override string ToString() { "8" ,"||:eight:||" }, }; - for (int i = 0; i < height; i++) + for (int i = 0; i < _height; i++) { - for (int j = 0; j < width; j++) + for (int j = 0; j < _width; j++) { - if (reponse.ContainsKey(grid[i, j])) - board += reponse[grid[i, j]]; + if (reponse.ContainsKey(_grid[i, j])) + board += reponse[_grid[i, j]]; else - board += grid[i, j]; + board += _grid[i, j]; } board += "\n"; } + return board; } } diff --git a/Floofbot/Modules/Logging.cs b/Floofbot/Modules/Logging.cs index 88bfad84..84facb6c 100644 --- a/Floofbot/Modules/Logging.cs +++ b/Floofbot/Modules/Logging.cs @@ -32,6 +32,7 @@ public class LoggerCommands : ModuleBase "UserMutedChannel", "UserUnmutedChannel", }; + private FloofDataContext _floofDB; public LoggerCommands(FloofDataContext floofDB) @@ -41,45 +42,78 @@ public LoggerCommands(FloofDataContext floofDB) private void AddServerIfNotExists(ulong serverId) { - // checks if server exists in database and adds if not - LogConfig serverConfig = _floofDB.LogConfigs.Find(serverId); - if (serverConfig == null) + // Checks if server exists in database and adds if not + var serverConfig = _floofDB.LogConfigs.Find(serverId); + + if (serverConfig != null) return; + + _floofDB.Add(new LogConfig { - _floofDB.Add(new LogConfig - { - ServerId = serverId, - MessageUpdatedChannel = 0, - MessageDeletedChannel = 0, - UserBannedChannel = 0, - UserUnbannedChannel = 0, - UserJoinedChannel = 0, - UserLeftChannel = 0, - MemberUpdatesChannel = 0, - UserKickedChannel = 0, - UserMutedChannel = 0, - UserUnmutedChannel = 0, - IsOn = false - }); - _floofDB.SaveChanges(); - } + ServerId = serverId, + MessageUpdatedChannel = 0, + MessageDeletedChannel = 0, + UserBannedChannel = 0, + UserUnbannedChannel = 0, + UserJoinedChannel = 0, + UserLeftChannel = 0, + MemberUpdatesChannel = 0, + UserKickedChannel = 0, + UserMutedChannel = 0, + UserUnmutedChannel = 0, + IsOn = false + }); + + _floofDB.SaveChanges(); + } + + private async Task AddServerIfNotExistsAsync(ulong serverId) + { + // Checks if server exists in database and adds if not + var serverConfig = _floofDB.LogConfigs.Find(serverId); + + if (serverConfig != null) return; + + _floofDB.Add(new LogConfig + { + ServerId = serverId, + MessageUpdatedChannel = 0, + MessageDeletedChannel = 0, + UserBannedChannel = 0, + UserUnbannedChannel = 0, + UserJoinedChannel = 0, + UserLeftChannel = 0, + MemberUpdatesChannel = 0, + UserKickedChannel = 0, + UserMutedChannel = 0, + UserUnmutedChannel = 0, + IsOn = false + }); + + await _floofDB.SaveChangesAsync(); } private bool TryLinkChannelType(string channelType, Discord.IChannel channel, Discord.IGuild guild) { - ulong serverId = guild.Id; - ulong channelId = channel == null ? 0 : channel.Id; + var serverId = guild.Id; + var channelId = channel?.Id ?? 0; + AddServerIfNotExists(serverId); + try { - string updateCommand = $"UPDATE LogConfigs SET {channelType} = {channelId} WHERE ServerID = {serverId}"; + var updateCommand = $"UPDATE LogConfigs SET {channelType} = {channelId} WHERE ServerID = {serverId}"; + _floofDB.Database.ExecuteSqlRaw(updateCommand); _floofDB.SaveChanges(); + return true; } catch (Exception e) { - string errorMsg = $"Error: Unable to link {channelType} to <#{channelId}>"; + var errorMsg = $"Error: Unable to link {channelType} to <#{channelId}>"; + Log.Error(errorMsg + Environment.NewLine + e); + return false; } } @@ -90,10 +124,8 @@ public async Task SetChannelType( [Summary("channel type")] string channelType = null, [Summary("channel (current channel if unspecified)")] Discord.IChannel channel = null) { - if (channel == null) - { - channel = (IChannel)Context.Channel; - } + // If channel is null we assign the Context.Channel + channel ??= Context.Channel; if (CHANNEL_TYPES.Contains(channelType)) { @@ -108,12 +140,13 @@ public async Task SetChannelType( } else { - EmbedBuilder builder = new EmbedBuilder() + var builder = new EmbedBuilder { Description = $"💾 Accepted channel types: ```{string.Join(", ", CHANNEL_TYPES)}```", Color = Color.Magenta }; - await Context.Channel.SendMessageAsync("", false, builder.Build()); + + await Context.Channel.SendMessageAsync(string.Empty, false, builder.Build()); } } @@ -134,12 +167,13 @@ public async Task UnsetChannelType([Summary("channel type")] string channelType } else { - EmbedBuilder builder = new EmbedBuilder() + var builder = new EmbedBuilder { Description = $"💾 Accepted channel types: ```{string.Join(", ", CHANNEL_TYPES)}```", Color = Color.Magenta }; - await Context.Channel.SendMessageAsync("", false, builder.Build()); + + await Context.Channel.SendMessageAsync(string.Empty, false, builder.Build()); } } @@ -147,20 +181,24 @@ public async Task UnsetChannelType([Summary("channel type")] string channelType [Command("toggle")] public async Task Toggle() { - AddServerIfNotExists(Context.Guild.Id); + await AddServerIfNotExistsAsync(Context.Guild.Id); + try { - LogConfig serverConfig = _floofDB.LogConfigs.Find(Context.Guild.Id); + var serverConfig = await _floofDB.LogConfigs.FindAsync(Context.Guild.Id); serverConfig.IsOn = !serverConfig.IsOn; - string statusString = serverConfig.IsOn ? "Enabled" : "Disabled"; + + var statusString = serverConfig.IsOn ? "Enabled" : "Disabled"; + await Context.Channel.SendMessageAsync($"Logger {statusString}!"); - _floofDB.SaveChanges(); + + await _floofDB.SaveChangesAsync(); } - catch (Exception ex) + catch (Exception e) { - await Context.Channel.SendMessageAsync("An error occured: " + ex.Message); - Log.Error("Error when trying to toggle the event logger: " + ex); - return; + await Context.Channel.SendMessageAsync("An error occured: " + e.Message); + + Log.Error("Error when trying to toggle the event logger: " + e); } } } diff --git a/Floofbot/Modules/ModMail.cs b/Floofbot/Modules/ModMail.cs index 52b99e13..1c4b01c3 100644 --- a/Floofbot/Modules/ModMail.cs +++ b/Floofbot/Modules/ModMail.cs @@ -1,11 +1,8 @@ using Discord; using Discord.Addons.Interactive; using Discord.Commands; -using Discord.WebSocket; using Floofbot.Configs; using Floofbot.Services.Repository; -using Floofbot.Services.Repository.Models; -using Serilog; using System; using System.Collections.Generic; using System.Linq; @@ -30,33 +27,35 @@ public ModMailModule(FloofDataContext _floofDB) [Command("")] [Name("modmail ")] [Summary("Send a message to the moderators")] - public async Task sendModMail([Summary("Message Content")][Remainder] string content = "") + public async Task SendModMail([Summary("Message Content")][Remainder] string content = "") { + var serverConfig = await _floofDb.ModMails.FindAsync(SERVER_ID); + try { if (string.IsNullOrEmpty(content)) { - EmbedBuilder b; - b = new EmbedBuilder() + var b = new EmbedBuilder() { Description = $"Usage: `modmail [message]`", Color = Color.Magenta }; - await Context.Message.Author.SendMessageAsync("", false, b.Build()); + + await Context.Message.Author.SendMessageAsync(string.Empty, false, b.Build()); } - // get values - ModMail serverConfig = _floofDb.ModMails.Find(SERVER_ID); - IGuild guild = Context.Client.GetGuild(SERVER_ID); // can return null - Discord.ITextChannel channel = await guild.GetTextChannelAsync((ulong)serverConfig.ChannelId); // can return null + // Get values + IGuild guild = Context.Client.GetGuild(SERVER_ID); // Can return null + var channel = await guild.GetTextChannelAsync((ulong)serverConfig.ChannelId); // Can return null IRole role = null; if (!Context.User.MutualGuilds.Contains(guild)) // the modmail server is not a mutual server return; - if (serverConfig == null || serverConfig.IsEnabled == false || guild == null || channel == null) // not configured + if (serverConfig == null || serverConfig.IsEnabled == false || guild == null || channel == null) // Not configured { await Context.Channel.SendMessageAsync("Modmail is not configured on this server."); + return; } @@ -71,156 +70,27 @@ public async Task sendModMail([Summary("Message Content")][Remainder] string con return; } - // form embed - SocketUser sender = Context.Message.Author; - EmbedBuilder builder = new EmbedBuilder() + // Form embed + var sender = Context.Message.Author; + + var builder = new EmbedBuilder() { Title = "⚠️ | MOD MAIL ALERT!", Description = $"Modmail from: {sender.Mention} ({sender.Username}#{sender.Discriminator})", Color = Discord.Color.Gold - }; - builder.WithCurrentTimestamp(); - builder.AddField("Message Content", $"```{content}```"); - string messageContent = (role == null) ? "Mod mail" : role.Mention; // role id can be set in database but deleted from server + }.WithCurrentTimestamp() + .AddField("Message Content", $"```{content}```"); + + var messageContent = (role == null) ? "Mod mail" : role.Mention; // role id can be set in database but deleted from server + await Context.Channel.SendMessageAsync("Alerting all mods!"); + await channel.SendMessageAsync(messageContent, false, builder.Build()); } - catch (Exception ex) + catch (Exception e) { - await Context.Channel.SendMessageAsync(ex.ToString()); - return; + await Context.Channel.SendMessageAsync(e.ToString()); } } } - [Summary("Modmail configuration commands")] - [RequireUserPermission(GuildPermission.Administrator)] - [Name("ModMail Config")] - [Group("modmailconfig")] - public class ModMailConfigModule : InteractiveBase - { - private FloofDataContext _floofDB; - - public ModMailConfigModule(FloofDataContext floofDB) - { - _floofDB = floofDB; - } - private Discord.Color GenerateColor() - { - return new Discord.Color((uint)new Random().Next(0x1000000)); - } - - private ModMail GetServerConfig(ulong server) - { - // checks if server exists in database and adds if not - var serverConfig = _floofDB.ModMails.Find(server); - if (serverConfig == null) - { - _floofDB.Add(new ModMail - { - ServerId = server, - ChannelId = null, - IsEnabled = false, - ModRoleId = null - }); - _floofDB.SaveChanges(); - return _floofDB.ModMails.Find(server); - } - else - { - return serverConfig; - } - } - - [Command("channel")] - [Summary("Sets the channel for the modmail notifications")] - public async Task Channel([Summary("Channel (eg #alerts)")]Discord.IChannel channel = null) - { - if (channel == null) - { - channel = (IChannel)Context.Channel; - } - var ServerConfig = GetServerConfig(Context.Guild.Id); - ServerConfig.ChannelId = channel.Id; - _floofDB.SaveChanges(); - await Context.Channel.SendMessageAsync("Channel updated! I will send modmails to <#" + channel.Id + ">"); - } - - [Command("toggle")] - [Summary("Toggles the modmail module")] - public async Task Toggle() - { - - // try toggling - try - { - // check the status of logger - var ServerConfig = GetServerConfig(Context.Guild.Id); - if (ServerConfig.ChannelId == null) - { - await Context.Channel.SendMessageAsync("Channel not set! Please set the channel before toggling the ModMail feature."); - return; - } - ServerConfig.IsEnabled = !ServerConfig.IsEnabled; - _floofDB.SaveChanges(); - await Context.Channel.SendMessageAsync("Modmail " + (ServerConfig.IsEnabled ? "Enabled!" : "Disabled!")); - } - catch (Exception ex) - { - await Context.Channel.SendMessageAsync("An error occured: " + ex.Message); - Log.Error("Error when trying to toggle the modmail: " + ex); - return; - } - } - [Command("modrole")] - [Summary("OPTIONAL: A Role to Ping When ModMail is Received.")] - public async Task SetModRole(string roleName = null) - { - if (roleName == null) - { - await Context.Channel.SendMessageAsync("", false, new EmbedBuilder { Description = $"💾 Usage: `modmailconfig [rolename]`", Color = GenerateColor() }.Build()); - return; - } - - var ServerConfig = GetServerConfig(Context.Guild.Id); - bool modRoleFound = false; - ulong? roleId = null; - try - { - foreach (SocketRole r in Context.Guild.Roles) - { - if (r.Name.ToLower() == roleName.ToLower()) - { - if (modRoleFound == false) // ok we found 1 role thats GOOD - { - modRoleFound = true; - roleId = r.Id; - } - else // there is more than 1 role with the same name! - { - await Context.Channel.SendMessageAsync("More than one role exists with that name! Not sure what to do! Please resolve this. Aborting.."); - return; - } - } - } - - if (modRoleFound) - { - ServerConfig.ModRoleId = roleId; - _floofDB.SaveChanges(); - await Context.Channel.SendMessageAsync("Mod role set!"); - } - else - { - await Context.Channel.SendMessageAsync("Unable to find that role. Role not set."); - return; - } - } - catch (Exception ex) - { - await Context.Channel.SendMessageAsync("An error occured: " + ex.Message); - Log.Error("Error when trying to set the modmail mod role: " + ex); - } - } - } - } diff --git a/Floofbot/Modules/ModMailConfigModule.cs b/Floofbot/Modules/ModMailConfigModule.cs new file mode 100644 index 00000000..396bdda1 --- /dev/null +++ b/Floofbot/Modules/ModMailConfigModule.cs @@ -0,0 +1,147 @@ +using System; +using System.Threading.Tasks; +using Discord; +using Discord.Addons.Interactive; +using Discord.Commands; +using Discord.WebSocket; +using Floofbot.Services.Repository; +using Floofbot.Services.Repository.Models; +using Serilog; + +namespace Floofbot.Modules +{ + [Summary("Modmail configuration commands")] + [RequireUserPermission(GuildPermission.Administrator)] + [Name("ModMail Config")] + [Group("modmailconfig")] + public class ModMailConfigModule : InteractiveBase + { + private FloofDataContext _floofDB; + + public ModMailConfigModule(FloofDataContext floofDB) + { + _floofDB = floofDB; + } + private Color GenerateColor() + { + return new Color((uint)new Random().Next(0x1000000)); + } + + private async Task GetServerConfigAsync(ulong server) + { + // Checks if server exists in database and adds if not + var serverConfig = _floofDB.ModMails.Find(server); + + if (serverConfig != null) return serverConfig; + + _floofDB.Add(new ModMail + { + ServerId = server, + ChannelId = null, + IsEnabled = false, + ModRoleId = null + }); + + await _floofDB.SaveChangesAsync(); + + return await _floofDB.ModMails.FindAsync(server); + } + + [Command("channel")] + [Summary("Sets the channel for the modmail notifications")] + public async Task Channel([Summary("Channel (eg #alerts)")]IChannel channel = null) + { + // If channel is null we assign the Context.Channel + channel ??= (IChannel) Context.Channel; + + var serverConfig = await GetServerConfigAsync(Context.Guild.Id); + serverConfig.ChannelId = channel.Id; + + await _floofDB.SaveChangesAsync(); + + await Context.Channel.SendMessageAsync("Channel updated! I will send modmails to <#" + channel.Id + ">"); + } + + [Command("toggle")] + [Summary("Toggles the modmail module")] + public async Task Toggle() + { + // Try toggling + try + { + // Check the status of logger + var serverConfig = await GetServerConfigAsync(Context.Guild.Id); + + if (serverConfig.ChannelId == null) + { + await Context.Channel.SendMessageAsync("Channel not set! Please set the channel before toggling the ModMail feature."); + return; + } + + serverConfig.IsEnabled = !serverConfig.IsEnabled; + + await _floofDB.SaveChangesAsync(); + + await Context.Channel.SendMessageAsync("Modmail " + (serverConfig.IsEnabled ? "Enabled!" : "Disabled!")); + } + catch (Exception e) + { + await Context.Channel.SendMessageAsync("An error occured: " + e.Message); + + Log.Error("Error when trying to toggle the modmail: " + e); + } + } + [Command("modrole")] + [Summary("OPTIONAL: A Role to Ping When ModMail is Received.")] + public async Task SetModRole(string roleName = null) + { + if (roleName == null) + { + await Context.Channel.SendMessageAsync(string.Empty, false, new EmbedBuilder { Description = $"💾 Usage: `modmailconfig [rolename]`", Color = GenerateColor() }.Build()); + return; + } + + var serverConfig = await GetServerConfigAsync(Context.Guild.Id); + var modRoleFound = false; + ulong? roleId = null; + + try + { + foreach (SocketRole r in Context.Guild.Roles) + { + if (r.Name.ToLower() != roleName.ToLower()) continue; + + if (modRoleFound == false) // OK, we found 1 role that's GOOD + { + modRoleFound = true; + roleId = r.Id; + } + else // There is more than 1 role with the same name! + { + await Context.Channel.SendMessageAsync("More than one role exists with that name! Not sure what to do! Please resolve this. Aborting.."); + return; + } + } + + if (modRoleFound) + { + serverConfig.ModRoleId = roleId; + + await _floofDB.SaveChangesAsync(); + + await Context.Channel.SendMessageAsync("Mod role set!"); + } + else + { + await Context.Channel.SendMessageAsync("Unable to find that role. Role not set."); + } + } + catch (Exception e) + { + await Context.Channel.SendMessageAsync("An error occured: " + e.Message); + + Log.Error("Error when trying to set the modmail mod role: " + e); + } + } + } +} \ No newline at end of file diff --git a/Floofbot/Modules/NicknameAlert.cs b/Floofbot/Modules/NicknameAlert.cs index 3d85dd1f..da5a57c4 100644 --- a/Floofbot/Modules/NicknameAlert.cs +++ b/Floofbot/Modules/NicknameAlert.cs @@ -21,30 +21,34 @@ public NicknameAlert(FloofDataContext floofDB) _floofDB = floofDB; } - private void CheckServerEntryExists(ulong server) + private async Task CheckServerEntryExistsAsync(ulong server) { - // checks if server exists in database and adds if not + // Checks if server exists in database and adds if not var serverConfig = _floofDB.NicknameAlertConfigs.Find(server); - if (serverConfig == null) + + if (serverConfig != null) return; + + _floofDB.Add(new NicknameAlertConfig { - _floofDB.Add(new NicknameAlertConfig - { - ServerId = server, - Channel = 0, - IsOn = false - }) ; - _floofDB.SaveChanges(); - } + ServerId = server, + Channel = 0, + IsOn = false + }); + + await _floofDB.SaveChangesAsync(); } [Command("setchannel")] // update into a group [Summary("Sets the channel for the nickname alerts")] public async Task Channel([Summary("Channel (eg #alerts)")]Discord.IChannel channel) { - CheckServerEntryExists(Context.Guild.Id); - var ServerConfig = _floofDB.NicknameAlertConfigs.Find(Context.Guild.Id); - ServerConfig.Channel = channel.Id; - _floofDB.SaveChanges(); + await CheckServerEntryExistsAsync(Context.Guild.Id); + + var serverConfig = _floofDB.NicknameAlertConfigs.Find(Context.Guild.Id); + serverConfig.Channel = channel.Id; + + await _floofDB.SaveChangesAsync(); + await Context.Channel.SendMessageAsync("Channel updated! I will send nickname alerts to <#" + channel.Id + ">"); } @@ -52,24 +56,24 @@ public async Task Channel([Summary("Channel (eg #alerts)")]Discord.IChannel chan [Summary("Toggles the nickname alerts")] public async Task Toggle() { - - // try toggling + // Try toggling try { - CheckServerEntryExists(Context.Guild.Id); - // check the status of logger - var ServerConfig = _floofDB.NicknameAlertConfigs.Find(Context.Guild.Id); - ServerConfig.IsOn = !ServerConfig.IsOn; - _floofDB.SaveChanges(); - await Context.Channel.SendMessageAsync("Nickname Alerts " + (ServerConfig.IsOn ? "Enabled!" : "Disabled!")); + await CheckServerEntryExistsAsync(Context.Guild.Id); + + // Check the status of logger + var serverConfig = _floofDB.NicknameAlertConfigs.Find(Context.Guild.Id); + serverConfig.IsOn = !serverConfig.IsOn; + + await _floofDB.SaveChangesAsync(); + + await Context.Channel.SendMessageAsync("Nickname Alerts " + (serverConfig.IsOn ? "Enabled!" : "Disabled!")); } - catch (Exception ex) + catch (Exception e) { - await Context.Channel.SendMessageAsync("An error occured: " + ex.Message); - Log.Error("Error when trying to toggle the nickname alerts: " + ex); - return; + await Context.Channel.SendMessageAsync("An error occured: " + e.Message); + Log.Error("Error when trying to toggle the nickname alerts: " + e); } } - } } diff --git a/Floofbot/Modules/RaidProtectionCommands.cs b/Floofbot/Modules/RaidProtectionCommands.cs index c6e73d53..97b5214c 100644 --- a/Floofbot/Modules/RaidProtectionCommands.cs +++ b/Floofbot/Modules/RaidProtectionCommands.cs @@ -25,76 +25,78 @@ public RaidProtectionCommands(FloofDataContext floofDB) { _floofDB = floofDB; } - private Discord.Color GenerateColor() + + private Color GenerateColor() { - return new Discord.Color((uint)new Random().Next(0x1000000)); + return new Color((uint)new Random().Next(0x1000000)); } - private RaidProtectionConfig GetServerConfig(ulong server) + private async Task GetServerConfigAsync(ulong server) { - // checks if server exists in database and adds if not + // Checks if server exists in database and adds if it is not var serverConfig = _floofDB.RaidProtectionConfigs.Find(server); - if (serverConfig == null) - { - _floofDB.Add(new RaidProtectionConfig - { - ServerId = server, - Enabled = false, - ModChannelId = null, - ModRoleId = null, - MutedRoleId = null, - BanOffenders = true, - ExceptionRoleId = null - }); - _floofDB.SaveChanges(); - return _floofDB.RaidProtectionConfigs.Find(server); - } - else - { - return serverConfig; - } + + if (serverConfig != null) return serverConfig; + + _floofDB.Add(new RaidProtectionConfig + { + ServerId = server, + Enabled = false, + ModChannelId = null, + ModRoleId = null, + MutedRoleId = null, + BanOffenders = true, + ExceptionRoleId = null + }); + + await _floofDB.SaveChangesAsync(); + + return await _floofDB.RaidProtectionConfigs.FindAsync(server); } - private async Task resolveRole(string input, SocketGuild guild, ISocketMessageChannel channel) + + private async Task ResolveRole(string input, SocketGuild guild, ISocketMessageChannel channel) { SocketRole role = null; - //resolve roleID or @mention + + // Resolve roleID or @mention if (Regex.IsMatch(input, @"\d{10,}")) { - string roleID = Regex.Match(input, @"\d{10,}").Value; + var roleID = Regex.Match(input, @"\d{10,}").Value; + role = guild.GetRole(Convert.ToUInt64(roleID)); } - //resolve role name - else + else // Resolve role name { foreach (SocketRole r in Context.Guild.Roles) { - if (r.Name.ToLower() == input.ToLower()) + if (r.Name.ToLower() != input.ToLower()) continue; + + if (role == null) // OK, we found 1 role that's GOOD { - if (role == null) // ok we found 1 role thats GOOD - { - role = r; - } - else // there is more than 1 role with the same name! - { - await channel.SendMessageAsync("More than one role exists with that name! Not sure what to do! Please resolve this. Aborting.."); - return null; - } + role = r; + } + else // There is more than 1 role with the same name! + { + await channel.SendMessageAsync("More than one role exists with that name! Not sure what to do! Please resolve this. Aborting.."); + return null; } } } - return role; + + return role; } + [Command("modchannel")] [Summary("OPTIONAL: Sets the mod channel for raid notifications")] - public async Task ModChannel([Summary("Channel (eg #alerts)")]Discord.IChannel channel = null) + public async Task ModChannel([Summary("Channel (eg #alerts)")]IChannel channel = null) { - if (channel == null) - { - channel = Context.Channel; - } - var ServerConfig = GetServerConfig(Context.Guild.Id); - ServerConfig.ModChannelId = channel.Id; - _floofDB.SaveChanges(); + channel ??= Context.Channel; + + var serverConfig = await GetServerConfigAsync(Context.Guild.Id); + serverConfig.ModChannelId = channel.Id; + + await _floofDB.SaveChangesAsync(); + await Context.Channel.SendMessageAsync("Channel updated! I will raid notifications to <#" + channel.Id + ">"); } @@ -103,114 +105,131 @@ public async Task ModChannel([Summary("Channel (eg #alerts)")]Discord.IChannel c public async Task Toggle() { // check the status of raid protection - var ServerConfig = GetServerConfig(Context.Guild.Id); - ServerConfig.Enabled = !ServerConfig.Enabled; - _floofDB.SaveChanges(); - await Context.Channel.SendMessageAsync("Raid Protection " + (ServerConfig.Enabled ? "Enabled!" : "Disabled!")); + var serverConfig = await GetServerConfigAsync(Context.Guild.Id); + serverConfig.Enabled = !serverConfig.Enabled; + + await _floofDB.SaveChangesAsync(); + + await Context.Channel.SendMessageAsync("Raid Protection " + (serverConfig.Enabled ? "Enabled!" : "Disabled!")); } + [Command("togglebans")] [Summary("Toggles whether to ban or mute users that trigger the raid detection.")] public async Task ToggleBans() { - - // try toggling + // Try toggling try { - // check the status of raid protection - var ServerConfig = GetServerConfig(Context.Guild.Id); - ServerConfig.BanOffenders = !ServerConfig.BanOffenders; - _floofDB.SaveChanges(); - await Context.Channel.SendMessageAsync("Raid Protection Bans " + (ServerConfig.BanOffenders ? "Enabled!" : "Disabled!")); + // Check the status of raid protection + var serverConfig = await GetServerConfigAsync(Context.Guild.Id); + serverConfig.BanOffenders = !serverConfig.BanOffenders; + + await _floofDB.SaveChangesAsync(); + + await Context.Channel.SendMessageAsync("Raid Protection Bans " + (serverConfig.BanOffenders ? "Enabled!" : "Disabled!")); } - catch (Exception ex) + catch (Exception e) { - await Context.Channel.SendMessageAsync("An error occured: " + ex.Message); - Log.Error("Error when trying to toggle raid protection banning: " + ex); - return; + await Context.Channel.SendMessageAsync("An error occured: " + e.Message); + Log.Error("Error when trying to toggle raid protection banning: " + e); } } + [Command("modrole")] [Summary("OPTIONAL: A role to ping for raid alerts.")] public async Task SetModRole(string role = null) { - var ServerConfig = GetServerConfig(Context.Guild.Id); + var serverConfig = await GetServerConfigAsync(Context.Guild.Id); if (role == null) { - ServerConfig.ModRoleId = null; - _floofDB.SaveChanges(); + serverConfig.ModRoleId = null; + + await _floofDB.SaveChangesAsync(); + await Context.Channel.SendMessageAsync("Mod role removed. I will no longer ping a mod role."); + return; } - var foundRole = await resolveRole(role, Context.Guild, Context.Channel); + var foundRole = await ResolveRole(role, Context.Guild, Context.Channel); if (foundRole != null) { - ServerConfig.ModRoleId = foundRole.Id; - _floofDB.SaveChanges(); + serverConfig.ModRoleId = foundRole.Id; + + await _floofDB.SaveChangesAsync(); + await Context.Channel.SendMessageAsync("Mod role set to " + foundRole.Name + "!"); } else { await Context.Channel.SendMessageAsync("Unable to set that role."); - return; } } [Command("mutedrole")] [Summary("OPTIONAL: A role to give to users to mute them in the server. If not set, users are banned by default.")] public async Task SetMutedRole(string role = null) { - var ServerConfig = GetServerConfig(Context.Guild.Id); + var serverConfig = await GetServerConfigAsync(Context.Guild.Id); if (role == null) { - ServerConfig.MutedRoleId = null; - _floofDB.SaveChanges(); + serverConfig.MutedRoleId = null; + + await _floofDB.SaveChangesAsync(); + await Context.Channel.SendMessageAsync("Muted role removed. I will now default to banning users."); + return; } - var foundRole = await resolveRole(role, Context.Guild, Context.Channel); + var foundRole = await ResolveRole(role, Context.Guild, Context.Channel); if (foundRole != null) { - ServerConfig.MutedRoleId = foundRole.Id; - _floofDB.SaveChanges(); + serverConfig.MutedRoleId = foundRole.Id; + + await _floofDB.SaveChangesAsync(); + await Context.Channel.SendMessageAsync("Muted role set to " + foundRole.Name + "!"); } else { await Context.Channel.SendMessageAsync("Unable to set that role."); - return; } } + [Command("exceptionsrole")] [Summary("OPTIONAL: Users with this role are immune to raid protection.")] public async Task SetExceptionsRole(string role = null) { - var ServerConfig = GetServerConfig(Context.Guild.Id); + var serverConfig = await GetServerConfigAsync(Context.Guild.Id); if (role == null) { - ServerConfig.ExceptionRoleId = null; - _floofDB.SaveChanges(); + serverConfig.ExceptionRoleId = null; + + await _floofDB.SaveChangesAsync(); + await Context.Channel.SendMessageAsync("Exceptions role removed. Users will no longer be exempt from raid protection."); + return; } - var foundRole = await resolveRole(role, Context.Guild, Context.Channel); + var foundRole = await ResolveRole(role, Context.Guild, Context.Channel); if (foundRole != null) { - ServerConfig.ExceptionRoleId = foundRole.Id; - _floofDB.SaveChanges(); + serverConfig.ExceptionRoleId = foundRole.Id; + + await _floofDB.SaveChangesAsync(); + await Context.Channel.SendMessageAsync("Exceptions role set to " + foundRole.Name + "!"); } else { await Context.Channel.SendMessageAsync("Unable to set that role."); - return; } } } diff --git a/Floofbot/Modules/TagCommands.cs b/Floofbot/Modules/TagCommands.cs index 549cfd6f..28bd79eb 100644 --- a/Floofbot/Modules/TagCommands.cs +++ b/Floofbot/Modules/TagCommands.cs @@ -17,10 +17,10 @@ namespace Floofbot.Modules [Name("Tag")] [Group("tag")] [RequireContext(ContextType.Guild)] - [RequireUserPermission(Discord.GuildPermission.AttachFiles)] + [RequireUserPermission(GuildPermission.AttachFiles)] public class TagCommands : InteractiveBase { - private static readonly Discord.Color EMBED_COLOR = Color.Magenta; + private static readonly Color EMBED_COLOR = Color.Magenta; private static readonly int TAGS_PER_PAGE = 20; private static readonly List SUPPORTED_IMAGE_EXTENSIONS = new List { @@ -39,11 +39,11 @@ public TagCommands(FloofDataContext floofDb) [RequireUserPermission(GuildPermission.Administrator)] public async Task RequireAdmin([Summary("True/False")] string requireAdmin = "") { - TagConfig config = await _floofDb.TagConfigs.AsQueryable() - .Where(config => config.ServerId == Context.Guild.Id).FirstOrDefaultAsync(); + var config = await _floofDb.TagConfigs.AsQueryable() + .Where(config => config.ServerId == Context.Guild.Id) + .FirstOrDefaultAsync(); - bool parsedRequireAdmin; - if (!bool.TryParse(requireAdmin, out parsedRequireAdmin)) + if (!bool.TryParse(requireAdmin, out var parsedRequireAdmin)) { await SendEmbed(CreateDescriptionEmbed($"Usage: `.tag requireadmin True/False`")); return; @@ -63,31 +63,37 @@ await _floofDb.TagConfigs.AddAsync(new TagConfig { config.TagUpdateRequiresAdmin = parsedRequireAdmin; } + await _floofDb.SaveChangesAsync(); - string message = "Adding/removing tags now " + + + var message = "Adding/removing tags now " + (parsedRequireAdmin ? "requires" : "does not require") + " admin permission."; + await Context.Channel.SendMessageAsync(message); } catch (DbUpdateException e) { - string message = "Error when configuring permissions for adding/removing tags."; + var message = "Error when configuring permissions for adding/removing tags."; await Context.Channel.SendMessageAsync(message); + Log.Error(message + Environment.NewLine + e); } } private bool GetTagUpdateRequiresAdmin(ulong serverId) { - // assume if the record doesn't exist in the DB that we do not need admin permission - TagConfig config = _floofDb.TagConfigs.AsQueryable() + // Assume if the record doesn't exist in the DB that we do not need admin permission + var config = _floofDb.TagConfigs.AsQueryable() .FirstOrDefault(config => config.ServerId == serverId); - return config == null ? false : config.TagUpdateRequiresAdmin; + + return config?.TagUpdateRequiresAdmin ?? false; } private bool UserHasTagUpdatePermissions(IGuildUser user) { - bool requireAdminPermissions = GetTagUpdateRequiresAdmin(Context.Guild.Id); + var requireAdminPermissions = GetTagUpdateRequiresAdmin(Context.Guild.Id); + return !requireAdminPermissions || user.GuildPermissions.BanMembers; } @@ -98,29 +104,34 @@ public async Task Add( [Summary("Tag name")] string tagName = null, [Summary("Tag content")][Remainder] string content = null) { - IGuildUser user = (IGuildUser)Context.Message.Author; + var user = (IGuildUser) Context.Message.Author; + if (!UserHasTagUpdatePermissions(user)) { await Context.Channel.SendMessageAsync("You do not have the permission to add tags."); return; } - if (!string.IsNullOrEmpty(tagName) && !string.IsNullOrEmpty(content)) + if (!string.IsNullOrWhiteSpace(tagName) && !string.IsNullOrWhiteSpace(content)) { - Regex rgx = new Regex("[^a-zA-Z0-9-]"); - string processedTagName = rgx.Replace(tagName, "").ToLower(); + var rgx = new Regex("[^a-zA-Z0-9-]"); + var processedTagName = rgx.Replace(tagName, string.Empty).ToLower(); + if (string.IsNullOrEmpty(processedTagName)) { await SendEmbed(CreateDescriptionEmbed($"💾 Invalid Tag name. " + "Tag must contain characters within [A-Za-z0-9-].")); + return; } - bool tagExists = _floofDb.Tags.AsQueryable() + var tagExists = _floofDb.Tags.AsQueryable() .Any(tag => tag.TagName == processedTagName && tag.ServerId == Context.Guild.Id); + if (tagExists) { await SendEmbed(CreateDescriptionEmbed($"💾 Tag `{processedTagName}` already exists")); + return; } @@ -133,7 +144,9 @@ await SendEmbed(CreateDescriptionEmbed($"💾 Invalid Tag name. " + UserId = Context.User.Id, TagContent = content }); - _floofDb.SaveChanges(); + + await _floofDb.SaveChangesAsync(); + await SendEmbed(CreateDescriptionEmbed($"💾 Added Tag `{processedTagName}`")); } catch (DbUpdateException e) @@ -155,7 +168,8 @@ public async Task Update( [Summary("Tag name")] string tagName = null, [Summary("Tag content")][Remainder] string content = null) { - IGuildUser user = (IGuildUser)Context.Message.Author; + var user = (IGuildUser)Context.Message.Author; + if (!UserHasTagUpdatePermissions(user)) { await Context.Channel.SendMessageAsync("You do not have the permission to update tags."); @@ -165,7 +179,7 @@ public async Task Update( if (!string.IsNullOrEmpty(tagName) && !string.IsNullOrEmpty(content)) { Regex rgx = new Regex("[^a-zA-Z0-9-]"); - string processedTagName = rgx.Replace(tagName, "").ToLower(); + string processedTagName = rgx.Replace(tagName, string.Empty).ToLower(); if (string.IsNullOrEmpty(processedTagName)) { await SendEmbed(CreateDescriptionEmbed($"💾 Invalid Tag name. " + @@ -173,13 +187,15 @@ await SendEmbed(CreateDescriptionEmbed($"💾 Invalid Tag name. " + return; } - Tag tag = _floofDb.Tags.AsQueryable() - .Where(tag => tag.TagName == processedTagName && tag.ServerId == Context.Guild.Id) - .FirstOrDefault(); + var tag = await _floofDb.Tags + .AsQueryable() + .FirstOrDefaultAsync(tag => tag.TagName == processedTagName && tag.ServerId == Context.Guild.Id); + if (tag == null) { await SendEmbed(CreateDescriptionEmbed($"💾 Tag `{processedTagName}` does not exist. " + "Please use the `tag add` command")); + return; } @@ -187,7 +203,9 @@ await SendEmbed(CreateDescriptionEmbed($"💾 Tag `{processedTagName}` does not { tag.UserId = Context.User.Id; tag.TagContent = content; - _floofDb.SaveChanges(); + + await _floofDb.SaveChangesAsync(); + await SendEmbed(CreateDescriptionEmbed($"💾 Updated Tag `{processedTagName}`")); } catch (DbUpdateException e) @@ -206,7 +224,7 @@ await SendEmbed(CreateDescriptionEmbed($"💾 Tag `{processedTagName}` does not [Summary("Lists all tags on the server, optionally filtering by keywords")] public async Task ListTags([Summary("Keywords to use")][Remainder] string keywords = null) { - List tags = _floofDb.Tags.AsQueryable() + var tags = _floofDb.Tags.AsQueryable() .Where(x => x.ServerId == Context.Guild.Id) .OrderBy(x => x.TagName) .ToList(); @@ -217,7 +235,7 @@ public async Task ListTags([Summary("Keywords to use")][Remainder] string keywor return; } - // filter tags by keywords if applicable + // Filter tags by keywords if applicable if (!string.IsNullOrEmpty(keywords)) { keywords = keywords.ToLower(); @@ -230,28 +248,31 @@ public async Task ListTags([Summary("Keywords to use")][Remainder] string keywor return; } - List pages = new List(); - int numPages = (int)Math.Ceiling((double)tags.Count / TAGS_PER_PAGE); - int tagIndex; - string actualName; + var pages = new List(); + var numPages = (int)Math.Ceiling((double)tags.Count / TAGS_PER_PAGE); + for (int i = 0; i < numPages; i++) { - string text = "```\n"; + var text = "```\n"; + for (int j = 0; j < TAGS_PER_PAGE; j++) { - tagIndex = i * TAGS_PER_PAGE + j; - if (tagIndex < tags.Count) - { - actualName = tags[tagIndex].TagName.Split(":")[0]; - text += $"{tagIndex + 1}. {actualName}\n"; - } + var tagIndex = (i * TAGS_PER_PAGE) + j; + + if (tagIndex >= tags.Count) continue; + + var actualName = tags[tagIndex].TagName.Split(":")[0]; + + text += $"{tagIndex + 1}. {actualName}\n"; } + text += "\n```"; + pages.Add(new PaginatedMessage.Page { Description = text }); - }; + } var pager = new PaginatedMessage { @@ -262,6 +283,7 @@ public async Task ListTags([Summary("Keywords to use")][Remainder] string keywor Options = PaginatedAppearanceOptions.Default, TimeStamp = DateTimeOffset.UtcNow }; + await PagedReplyAsync(pager, new ReactionList { Forward = true, @@ -276,7 +298,8 @@ public async Task ListTags([Summary("Keywords to use")][Remainder] string keywor [RequireUserPermission(GuildPermission.AttachFiles)] public async Task Remove([Summary("Tag name")] string tag = null) { - IGuildUser user = (IGuildUser)Context.Message.Author; + var user = (IGuildUser)Context.Message.Author; + if (!UserHasTagUpdatePermissions(user)) { await Context.Channel.SendMessageAsync("You do not have the permission to remove tags."); @@ -285,8 +308,9 @@ public async Task Remove([Summary("Tag name")] string tag = null) if (!string.IsNullOrEmpty(tag)) { - string tagName = tag.ToLower(); - Tag tagToRemove = _floofDb.Tags.FirstOrDefault(x => x.TagName == tagName && x.ServerId == Context.Guild.Id); + var tagName = tag.ToLower(); + var tagToRemove = _floofDb.Tags.FirstOrDefault(x => x.TagName == tagName && x.ServerId == Context.Guild.Id); + if (tagToRemove != null) { try @@ -321,20 +345,21 @@ public async Task GetTag([Summary("Tag name")] string tagName = "") if (!string.IsNullOrEmpty(tagName)) { tagName = tagName.ToLower(); - Tag selectedTag = _floofDb.Tags.AsQueryable().FirstOrDefault(x => x.TagName == tagName && x.ServerId == Context.Guild.Id); + var selectedTag = _floofDb.Tags.AsQueryable().FirstOrDefault(x => x.TagName == tagName && x.ServerId == Context.Guild.Id); if (selectedTag != null) { - string mentionlessTagContent = selectedTag.TagContent.Replace("@", "[at]"); + var mentionlessTagContent = selectedTag.TagContent.Replace("@", "[at]"); - bool isImage = false; + var isImage = false; + if (Uri.IsWellFormedUriString(mentionlessTagContent, UriKind.RelativeOrAbsolute)) { string ext = mentionlessTagContent.Split('.').Last().ToLower(); isImage = SUPPORTED_IMAGE_EXTENSIONS.Contains(ext); } - // tag found, so post it + // Tag found, so post it if (isImage) { EmbedBuilder builder = new EmbedBuilder() @@ -352,30 +377,29 @@ public async Task GetTag([Summary("Tag name")] string tagName = "") } else { - // tag not found + // Tag not found await SendEmbed(CreateDescriptionEmbed($"💾 Could not find Tag: `{tagName}`")); } } else { - // no tag given + // No tag given await SendEmbed(CreateDescriptionEmbed($"💾 Usage: `tag [name]`")); } } private Embed CreateDescriptionEmbed(string description) { - EmbedBuilder builder = new EmbedBuilder + return new EmbedBuilder { Description = description, Color = EMBED_COLOR - }; - return builder.Build(); + }.Build(); } private async Task SendEmbed(Embed embed) { - await Context.Channel.SendMessageAsync("", false, embed); + await Context.Channel.SendMessageAsync(string.Empty, false, embed); } } } diff --git a/Floofbot/Modules/UserAssignableRoleAdminCommands.cs b/Floofbot/Modules/UserAssignableRoleAdminCommands.cs new file mode 100644 index 00000000..4471f02f --- /dev/null +++ b/Floofbot/Modules/UserAssignableRoleAdminCommands.cs @@ -0,0 +1,136 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Discord; +using Discord.Addons.Interactive; +using Discord.Commands; +using Discord.WebSocket; +using Floofbot.Services.Repository; +using Floofbot.Services.Repository.Models; +using Serilog; + +namespace Floofbot.Modules +{ + [Summary("Configures the joinable user roles")] + [Name("User Assignable Roles Configuration")] + [RequireUserPermission(GuildPermission.Administrator)] + [Group("iamconfig")] + public class UserAssignableRoleAdminCommands : InteractiveBase + { + FloofDataContext _floofDb; + private readonly Color EMBED_COLOUR = new Color((uint)new Random().Next(0x1000000)); + + public UserAssignableRoleAdminCommands(FloofDataContext floofDb) + { + _floofDb = floofDb; + } + + private SocketRole GetRole(string roleName, SocketGuild guild) + { + foreach (SocketRole r in guild.Roles) + { + if (r.Name.ToLower() == roleName) + return r; + } + return null; + } + + private bool CheckRoleEntryExists(SocketRole role, SocketGuild guild) + { + // checks if a word exists in the filter db + var isRoleInDb = _floofDb.UserAssignableRoles.AsQueryable().Where(r => r.ServerId == guild.Id).Any(r => r.RoleId == role.Id); + + return isRoleInDb; + } + + [Summary("Configure a role as joinable")] + [Command("add")] + public async Task AddRole([Summary("role name")][Remainder]string roleName = null) + { + if (roleName == null) + { + await Context.Channel.SendMessageAsync(string.Empty, false, new EmbedBuilder { Description = $"💾 Usage: `iamconfig add [rolename]`", Color = EMBED_COLOUR }.Build()); + return; + } + + // Find the role by iterating over guild roles + var role = GetRole(roleName.ToLower(), Context.Guild); + + if (role == null) + { + await Context.Channel.SendMessageAsync("Unable to find a role with that name to add."); + return; + } + + var roleExistsInDb = CheckRoleEntryExists(role, Context.Guild); + + if (roleExistsInDb) + { + await Context.Channel.SendMessageAsync("Users can already join that role."); + return; + } + + try + { + _floofDb.Add(new UserAssignableRole + { + RoleId = role.Id, + ServerId = Context.Guild.Id + }); + + await _floofDb.SaveChangesAsync(); + + await Context.Channel.SendMessageAsync($"{role.Name} ({role.Id}) can now be joined by users!"); + } + catch (Exception e) + { + await Context.Channel.SendMessageAsync("An error occured when trying to add that role."); + + Log.Error("Error when trying to add a joinable role: " + e); + } + } + + [Summary("Configure a role to no longer be joinable")] + [Command("remove")] + public async Task RemoveRole([Summary("role name")][Remainder]string roleName = null) + { + if (roleName == null) + { + await Context.Channel.SendMessageAsync(string.Empty, false, new EmbedBuilder { Description = $"💾 Usage: `iamconfig remove [rolename]`", Color = EMBED_COLOUR }.Build()); + return; + } + + // Find the role by iterating over guild roles + var role = GetRole(roleName.ToLower(), Context.Guild); + + if (role == null) + { + await Context.Channel.SendMessageAsync("Unable to find a role with that name to remove."); + return; + } + + var roleExistsInDb = CheckRoleEntryExists(role, Context.Guild); + if (!roleExistsInDb) + { + await Context.Channel.SendMessageAsync("Users already cannot join that role."); + return; + } + + try + { + var roleDbEntry = _floofDb.UserAssignableRoles.AsQueryable().Where(r => r.ServerId == Context.Guild.Id).First(r => r.RoleId == role.Id); + + _floofDb.Remove(roleDbEntry); + + await _floofDb.SaveChangesAsync(); + + await Context.Channel.SendMessageAsync($"{role.Name} ({role.Id}) can no longer be joined!"); + } + catch (Exception e) + { + await Context.Channel.SendMessageAsync("An error occured when trying to remove that role: " + e.Message); + Log.Error("Error when trying to remove a joinable role " + e); + } + } + } +} \ No newline at end of file diff --git a/Floofbot/Modules/UserAssignableRoleCommands.cs b/Floofbot/Modules/UserAssignableRoleCommands.cs index 1537b038..7e1be82e 100644 --- a/Floofbot/Modules/UserAssignableRoleCommands.cs +++ b/Floofbot/Modules/UserAssignableRoleCommands.cs @@ -4,7 +4,6 @@ using Discord.Commands; using System.Text.RegularExpressions; using Floofbot.Services.Repository; -using Floofbot.Services.Repository.Models; using System.Linq; using Discord.Addons.Interactive; using Discord.WebSocket; @@ -23,9 +22,9 @@ public UserAssignableRoleCommands(FloofDataContext floofDb) _floofDb = floofDb; } - private Discord.Color GenerateColor() + private Color GenerateColor() { - return new Discord.Color((uint)new Random().Next(0x1000000)); + return new Color((uint)new Random().Next(0x1000000)); } private SocketRole GetRole(string roleName, SocketGuild guild) @@ -35,12 +34,14 @@ private SocketRole GetRole(string roleName, SocketGuild guild) if (r.Name.ToLower() == roleName) return r; } + return null; } private bool CheckRoleEntryExists(SocketRole role, SocketGuild guild) { - // checks if a word exists in the filter db - bool isRoleInDb = _floofDb.UserAssignableRoles.AsQueryable().Where(r => r.ServerId == guild.Id).Where(r => r.RoleId == role.Id).Any(); + // Checks if a word exists in the filter db + var isRoleInDb = _floofDb.UserAssignableRoles.AsQueryable().Where(r => r.ServerId == guild.Id).Any(r => r.RoleId == role.Id); + return isRoleInDb; } @@ -50,49 +51,50 @@ public async Task IAm([Summary("role name")][Remainder]string roleName = null) { if (roleName == null) { - await Context.Channel.SendMessageAsync("", false, new EmbedBuilder { Description = $"💾 Usage: `iam [rolename]`", Color = GenerateColor() }.Build()); + await Context.Channel.SendMessageAsync(string.Empty, false, new EmbedBuilder { Description = $"💾 Usage: `iam [rolename]`", Color = GenerateColor() }.Build()); + return; } - // find the role by iterating over guild roles - SocketRole role = GetRole(roleName.ToLower(), Context.Guild); + // Find the role by iterating over guild roles + var role = GetRole(roleName.ToLower(), Context.Guild); + if (role == null) { await Context.Channel.SendMessageAsync("Unable to find a role with that name."); return; } - // check that they are actually allowed to join the role - bool roleExistsInDb = CheckRoleEntryExists(role, Context.Guild); + // Check that they are actually allowed to join the role + var roleExistsInDb = CheckRoleEntryExists(role, Context.Guild); + if (roleExistsInDb == false) { await Context.Channel.SendMessageAsync("You cannot join that role."); return; } - IGuildUser usr = Context.User as IGuildUser; + var usr = Context.User as IGuildUser; + foreach (ulong r in usr.RoleIds) { - if (r == role.Id) - { - await Context.Channel.SendMessageAsync("You already have that role."); - return; - } + if (r != role.Id) continue; + + await Context.Channel.SendMessageAsync("You already have that role."); + return; } - // try to add the role onto the user + + // Try to add the role onto the user try { await usr.AddRoleAsync(role, new RequestOptions { AuditLogReason = "User assigned themselves the role" }); await Context.Channel.SendMessageAsync($"You now have the '{role.Name}' role."); - return; } - catch (Exception ex) + catch (Exception e) { await Context.Channel.SendMessageAsync("Unable to give you that role. I may not have the permissions."); - Log.Error("Error trying to add a role onto a user: " + ex); - return; + Log.Error("Error trying to add a role onto a user: " + e); } - } [Summary("Remove a role")] @@ -101,12 +103,13 @@ public async Task IAmNot([Summary("role name")][Remainder]string roleName = null { if (roleName == null) { - await Context.Channel.SendMessageAsync("", false, new EmbedBuilder { Description = $"💾 Usage: `iamnot [rolename]`", Color = GenerateColor() }.Build()); + await Context.Channel.SendMessageAsync(string.Empty, false, new EmbedBuilder { Description = $"💾 Usage: `iamnot [rolename]`", Color = GenerateColor() }.Build()); return; } - // find the role by iterating over guild roles - SocketRole role = GetRole(roleName.ToLower(), Context.Guild); + // Find the role by iterating over guild roles + var role = GetRole(roleName.ToLower(), Context.Guild); + if (role == null) { await Context.Channel.SendMessageAsync("Unable to find a role with that name."); @@ -114,7 +117,8 @@ public async Task IAmNot([Summary("role name")][Remainder]string roleName = null } // check that the role exists in the database as a joinable roll - bool roleExistsInDb = CheckRoleEntryExists(role, Context.Guild); + var roleExistsInDb = CheckRoleEntryExists(role, Context.Guild); + if (roleExistsInDb == false) { await Context.Channel.SendMessageAsync("You cannot leave that role."); @@ -122,142 +126,32 @@ public async Task IAmNot([Summary("role name")][Remainder]string roleName = null } // if they have the role we remove it - IGuildUser usr = Context.User as IGuildUser; + var usr = Context.User as IGuildUser; + foreach (ulong r in usr.RoleIds) { - if (r == role.Id) - { - try - { - await usr.RemoveRoleAsync(role, new RequestOptions { AuditLogReason = "User removed the role themselves." }); - await Context.Channel.SendMessageAsync($"You no longer have the '{role.Name}' role."); - return; - } - catch (Exception ex) - { - await Context.Channel.SendMessageAsync("Unable to remove that role. I may not have the permissions."); - Log.Error("Error trying to remove a role from a user: " + ex); - return; - } - } - } - // user does not have role - await Context.Channel.SendMessageAsync("You do not have that role."); - } - } - - [Summary("Configures the joinable user roles")] - [Name("User Assignable Roles Configuration")] - [RequireUserPermission(GuildPermission.Administrator)] - [Group("iamconfig")] - public class UserAssignableRoleAdminCommands : InteractiveBase - { - FloofDataContext _floofDb; - private readonly Discord.Color EMBED_COLOUR = new Discord.Color((uint)new Random().Next(0x1000000)); - public UserAssignableRoleAdminCommands(FloofDataContext floofDb) - { - _floofDb = floofDb; - } - private SocketRole GetRole(string roleName, SocketGuild guild) - { - foreach (SocketRole r in guild.Roles) - { - if (r.Name.ToLower() == roleName) - return r; - } - return null; - } - private bool CheckRoleEntryExists(SocketRole role, SocketGuild guild) - { - // checks if a word exists in the filter db - bool isRoleInDb = _floofDb.UserAssignableRoles.AsQueryable().Where(r => r.ServerId == guild.Id).Where(r => r.RoleId == role.Id).Any(); - return isRoleInDb; - } - - [Summary("Configure a role as joinable")] - [Command("add")] - public async Task AddRole([Summary("role name")][Remainder]string roleName = null) - { - if (roleName == null) - { - await Context.Channel.SendMessageAsync("", false, new EmbedBuilder { Description = $"💾 Usage: `iamconfig add [rolename]`", Color = EMBED_COLOUR }.Build()); - return; - } - - // find the role by iterating over guild roles - SocketRole role = GetRole(roleName.ToLower(), Context.Guild); - if (role == null) - { - await Context.Channel.SendMessageAsync("Unable to find a role with that name to add."); - return; - } - - bool roleExistsInDb = CheckRoleEntryExists(role, Context.Guild); - if (roleExistsInDb == true) - { - await Context.Channel.SendMessageAsync("Users can already join that role."); - return; - } - else - { + if (r != role.Id) continue; + try { - _floofDb.Add(new UserAssignableRole - { - RoleId = role.Id, - ServerId = Context.Guild.Id - }); - _floofDb.SaveChanges(); - await Context.Channel.SendMessageAsync($"{role.Name} ({role.Id}) can now be joined by users!"); - } - catch (Exception ex) - { - await Context.Channel.SendMessageAsync("An error occured when trying to add that role."); - Log.Error("Error when trying to add a joinable role: " + ex); - } - } - } - - [Summary("Configure a role to no longer be joinable")] - [Command("remove")] - public async Task RemoveRole([Summary("role name")][Remainder]string roleName = null) - { - if (roleName == null) - { - await Context.Channel.SendMessageAsync("", false, new EmbedBuilder { Description = $"💾 Usage: `iamconfig remove [rolename]`", Color = EMBED_COLOUR }.Build()); - return; - } - - // find the role by iterating over guild roles - SocketRole role = GetRole(roleName.ToLower(), Context.Guild); - if (role == null) - { - await Context.Channel.SendMessageAsync("Unable to find a role with that name to remove."); - return; - } - - bool roleExistsInDb = CheckRoleEntryExists(role, Context.Guild); - if (roleExistsInDb == false) - { - await Context.Channel.SendMessageAsync("Users already cannot join that role."); - return; - } - else - { - try - { - UserAssignableRole roleDbEntry = _floofDb.UserAssignableRoles.AsQueryable().Where(r => r.ServerId == Context.Guild.Id).Where(r => r.RoleId == role.Id).First(); - _floofDb.Remove(roleDbEntry); - _floofDb.SaveChanges(); - await Context.Channel.SendMessageAsync($"{role.Name} ({role.Id}) can no longer be joined!"); + await usr.RemoveRoleAsync(role, new RequestOptions { AuditLogReason = "User removed the role themselves." }); + + await Context.Channel.SendMessageAsync($"You no longer have the '{role.Name}' role."); + + return; } - catch (Exception ex) + catch (Exception e) { - await Context.Channel.SendMessageAsync("An error occured when trying to remove that role: " + ex.Message); - Log.Error("Error when trying to remove a joinable role " + ex); + await Context.Channel.SendMessageAsync("Unable to remove that role. I may not have the permissions."); + + Log.Error("Error trying to remove a role from a user: " + e); + + return; } } - + + // User does not have role + await Context.Channel.SendMessageAsync("You do not have that role."); } } } diff --git a/Floofbot/Modules/Utilities.cs b/Floofbot/Modules/Utilities.cs index cfc9509a..c8b41447 100644 --- a/Floofbot/Modules/Utilities.cs +++ b/Floofbot/Modules/Utilities.cs @@ -18,25 +18,28 @@ namespace Floofbot [Name("Utilities")] public class Utilities : InteractiveBase { - private static readonly Discord.Color EMBED_COLOR = Color.Magenta; + private static readonly Color EMBED_COLOR = Color.Magenta; [Command("ping")] [Summary("Responds with the ping in milliseconds")] public async Task Ping() { var sw = Stopwatch.StartNew(); + var msg = await Context.Channel.SendMessageAsync(":owl:").ConfigureAwait(false); + sw.Stop(); + await msg.DeleteAsync(); - EmbedBuilder builder = new EmbedBuilder() + var builder = new EmbedBuilder() { Title = "Butts!", Description = $"📶 Reply: `{(int)sw.Elapsed.TotalMilliseconds}ms`", Color = EMBED_COLOR }; - await Context.Channel.SendMessageAsync("", false, builder.Build()); + await Context.Channel.SendMessageAsync(string.Empty, false, builder.Build()); } [Command("userinfo")] @@ -50,17 +53,17 @@ public async Task UserInfo(IGuildUser usr = null) if (user == null) return; - string avatar = "https://cdn.discordapp.com/attachments/440635657925165060/442039889475665930/Turqouise.jpg"; - + var avatar = "https://cdn.discordapp.com/attachments/440635657925165060/442039889475665930/Turqouise.jpg"; // Get user's Discord joining date and time, in UTC - string discordJoin = user.CreatedAt.ToUniversalTime().ToString("dd\\/MMM\\/yyyy \\a\\t H:MM \\U\\T\\C"); + var discordJoin = user.CreatedAt.ToUniversalTime().ToString("dd\\/MMM\\/yyyy \\a\\t H:MM \\U\\T\\C"); // Get user's Guild joining date and time, in UTC - string guildJoin = user.JoinedAt?.ToUniversalTime().ToString("dd\\/MMM\\/yyyy \\a\\t H:MM \\U\\T\\C"); + var guildJoin = user.JoinedAt?.ToUniversalTime().ToString("dd\\/MMM\\/yyyy \\a\\t H:MM \\U\\T\\C"); if (user.AvatarId != null) avatar = user.GetAvatarUrl(ImageFormat.Auto, 512); - string infostring = $"👥 **User info for {user.Mention}** \n"; + var infostring = $"👥 **User info for {user.Mention}** \n"; + infostring += $"**User** : {user.Nickname ?? user.Username} ({user.Username}#{user.Discriminator})\n" + $"**ID** : {user.Id}\n" + @@ -68,14 +71,14 @@ public async Task UserInfo(IGuildUser usr = null) $"**Guild Join Date** : {guildJoin}\n" + $"**Status** : {user.Status}\n"; - EmbedBuilder builder = new EmbedBuilder + var builder = new EmbedBuilder { ThumbnailUrl = avatar, Description = infostring, Color = EMBED_COLOR }; - await Context.Channel.SendMessageAsync("", false, builder.Build()); + await Context.Channel.SendMessageAsync(string.Empty, false, builder.Build()); } [Command("avatar")] @@ -84,18 +87,19 @@ public async Task UserInfo(IGuildUser usr = null) [RequireBotPermission(ChannelPermission.EmbedLinks)] public async Task Avatar([Remainder] IGuildUser user = null) { - if (user == null) - user = (IGuildUser)Context.User; + // If user is null, we assign Context.User to it + user ??= (IGuildUser) Context.User; var avatarUrl = user.GetAvatarUrl(ImageFormat.Auto, 512); - EmbedBuilder builder = new EmbedBuilder() + + var builder = new EmbedBuilder() { Description = $"🖼️ **Avatar for:** {user.Mention}\n", ImageUrl = avatarUrl, Color = EMBED_COLOR - }; - await Context.Channel.SendMessageAsync("", false, builder.Build()); + + await Context.Channel.SendMessageAsync(string.Empty, false, builder.Build()); } [Command("embed")] @@ -111,7 +115,8 @@ public async Task RepeatMessage([Remainder] string message =null) Description = message, Color = EMBED_COLOR }; - await Context.Channel.SendMessageAsync("", false, builder.Build()); + + await Context.Channel.SendMessageAsync(string.Empty, false, builder.Build()); await Context.Channel.DeleteMessageAsync(Context.Message.Id); } else @@ -142,41 +147,39 @@ public async Task EchoMessage([Remainder] string message = null) [RequireBotPermission(ChannelPermission.EmbedLinks)] public async Task ServerInfo() { - SocketGuild guild = Context.Guild; + var guild = Context.Guild; // Get Guild creation date and time, in UTC - string guildCreated = guild.CreatedAt.ToUniversalTime().ToString("dd\\/MMM\\/yyyy \\a\\t H:MM \\U\\T\\C"); - - int numberTextChannels = guild.TextChannels.Count; - int numberVoiceChannels = guild.VoiceChannels.Count; - int daysOld = Context.Message.CreatedAt.Subtract(guild.CreatedAt).Days; - string daysAgo = $" That's " + ((daysOld == 0) ? "today!" : (daysOld == 1) ? $"yesterday!" : $"{daysOld} days ago!"); - string createdAt = $"Created {guildCreated}." + daysAgo; - int totalMembers = guild.MemberCount; - int onlineUsers = guild.Users.Where(mem => mem.Status == UserStatus.Online).Count(); - int numberRoles = guild.Roles.Count; - int numberEmojis = guild.Emotes.Count; - uint colour = (uint)new Random().Next(0x1000000); // random hex - - EmbedBuilder embed = new EmbedBuilder(); - - embed.WithDescription(createdAt) - .WithColor(new Discord.Color(colour)) - .AddField("Users (Online/Total)", $"{onlineUsers}/{totalMembers}", true) - .AddField("Text Channels", numberTextChannels, true) - .AddField("Voice Channels", numberVoiceChannels, true) - .AddField("Roles", numberRoles, true) - .AddField("Emojis", numberEmojis, true) - .AddField("Owner", $"{guild.Owner.Username}#{guild.Owner.Discriminator}", true) - .WithFooter($"Server ID: {guild.Id}") - .WithAuthor(guild.Name) - .WithCurrentTimestamp(); + var guildCreated = guild.CreatedAt.ToUniversalTime().ToString("dd\\/MMM\\/yyyy \\a\\t H:MM \\U\\T\\C"); + + var numberTextChannels = guild.TextChannels.Count; + var numberVoiceChannels = guild.VoiceChannels.Count; + var daysOld = Context.Message.CreatedAt.Subtract(guild.CreatedAt).Days; + var daysAgo = $" That's " + ((daysOld == 0) ? "today!" : (daysOld == 1) ? $"yesterday!" : $"{daysOld} days ago!"); + var createdAt = $"Created {guildCreated}." + daysAgo; + var totalMembers = guild.MemberCount; + var onlineUsers = guild.Users.Count(mem => mem.Status == UserStatus.Online); + var numberRoles = guild.Roles.Count; + var numberEmojis = guild.Emotes.Count; + var colour = (uint)new Random().Next(0x1000000); // random hex + + var embed = new EmbedBuilder() + .WithDescription(createdAt) + .WithColor(new Color(colour)) + .AddField("Users (Online/Total)", $"{onlineUsers}/{totalMembers}", true) + .AddField("Text Channels", numberTextChannels, true) + .AddField("Voice Channels", numberVoiceChannels, true) + .AddField("Roles", numberRoles, true) + .AddField("Emojis", numberEmojis, true) + .AddField("Owner", $"{guild.Owner.Username}#{guild.Owner.Discriminator}", true) + .WithFooter($"Server ID: {guild.Id}") + .WithAuthor(guild.Name) + .WithCurrentTimestamp(); if (Uri.IsWellFormedUriString(guild.IconUrl, UriKind.Absolute)) embed.WithThumbnailUrl(guild.IconUrl); - await Context.Channel.SendMessageAsync("", false, embed.Build()); - + await Context.Channel.SendMessageAsync(string.Empty, false, embed.Build()); } [RequireOwner] @@ -198,6 +201,7 @@ public async Task ReloadConfig() { await Context.Client.SetActivityAsync(BotConfigFactory.Config.Activity); } + await Context.Channel.SendMessageAsync("Config reloaded successfully"); } @@ -206,31 +210,32 @@ public async Task ReloadConfig() [RequireOwner] public async Task ServerList() { - List guilds = new List(Context.Client.Guilds); - List pages = new List(); + var guilds = new List(Context.Client.Guilds); + var pages = new List(); - foreach (SocketGuild g in guilds) + foreach (var g in guilds) { - List fields = new List(); - - fields.Add(new EmbedFieldBuilder() + var fields = new List { - Name = $"Owner", - Value = $"{g.Owner.Username}#{g.Owner.Discriminator} | ``{g.Owner.Id}``", - IsInline = false - }); - fields.Add(new EmbedFieldBuilder() - { - Name = $"Server ID", - Value = g.Id, - IsInline = false - }); - fields.Add(new EmbedFieldBuilder() - { - Name = $"Members", - Value = g.MemberCount, - IsInline = false - }); + new EmbedFieldBuilder() + { + Name = $"Owner", + Value = $"{g.Owner.Username}#{g.Owner.Discriminator} | ``{g.Owner.Id}``", + IsInline = false + }, + new EmbedFieldBuilder() + { + Name = $"Server ID", + Value = g.Id, + IsInline = false + }, + new EmbedFieldBuilder() + { + Name = $"Members", + Value = g.MemberCount, + IsInline = false + } + }; pages.Add(new PaginatedMessage.Page { @@ -239,6 +244,7 @@ public async Task ServerList() ThumbnailUrl = (Uri.IsWellFormedUriString(g.IconUrl, UriKind.Absolute) ? g.IconUrl : null) }); } + var pager = new PaginatedMessage { Pages = pages, @@ -248,37 +254,38 @@ public async Task ServerList() Options = PaginatedAppearanceOptions.Default, TimeStamp = DateTimeOffset.UtcNow }; + await PagedReplyAsync(pager, new ReactionList { Forward = true, Backward = true, Jump = true, Trash = true - }, true); + }); } [Command("convert")] [Alias("conv")] [Summary("Converts between Temperature, Length, and Mass units.")] - public async Task convert([Remainder] string input = "") + public async Task Convert([Remainder] string input = "") { - Regex fahReg = new Regex(@"\d+(?=f)", RegexOptions.IgnoreCase); - Regex celReg = new Regex(@"\d+(?=c)", RegexOptions.IgnoreCase); - Regex miReg = new Regex(@"\d+(?=mi)", RegexOptions.IgnoreCase); - Regex kmReg = new Regex(@"\d+(?=km)", RegexOptions.IgnoreCase); - Regex kgReg = new Regex(@"\d+(?=kg)", RegexOptions.IgnoreCase); - Regex lbReg = new Regex(@"\d+(?=lbs)", RegexOptions.IgnoreCase); - string embedDesc = ""; - int okInt = 0; //This will be used to check for command success + var fahReg = new Regex(@"\d+(?=f)", RegexOptions.IgnoreCase); + var celReg = new Regex(@"\d+(?=c)", RegexOptions.IgnoreCase); + var miReg = new Regex(@"\d+(?=mi)", RegexOptions.IgnoreCase); + var kmReg = new Regex(@"\d+(?=km)", RegexOptions.IgnoreCase); + var kgReg = new Regex(@"\d+(?=kg)", RegexOptions.IgnoreCase); + var lbReg = new Regex(@"\d+(?=lbs)", RegexOptions.IgnoreCase); + var embedDesc = string.Empty; + var okInt = 0; // This will be used to check for command success if (fahReg.IsMatch(input)) { okInt++; - double fahTmp = Convert.ToDouble(Regex.Match(input, @"-?\d+(?=f)").Value); + var fahTmp = System.Convert.ToDouble(Regex.Match(input, @"-?\d+(?=f)").Value); - Temperature fah = Temperature.FromDegreesFahrenheit(fahTmp); - double cel = Math.Round(fah.DegreesCelsius, 2, MidpointRounding.ToEven); + var fah = Temperature.FromDegreesFahrenheit(fahTmp); + var cel = Math.Round(fah.DegreesCelsius, 2, MidpointRounding.ToEven); embedDesc += $"🌡 {fah} is equal to {cel}°C.\n"; } @@ -287,10 +294,10 @@ public async Task convert([Remainder] string input = "") { okInt++; - double celTmp = Convert.ToDouble(Regex.Match(input, @"-?\d+(?=c)").Value); + var celTmp = System.Convert.ToDouble(Regex.Match(input, @"-?\d+(?=c)").Value); - Temperature cel = Temperature.FromDegreesCelsius(celTmp); - double fah = Math.Round(cel.DegreesFahrenheit, 2, MidpointRounding.ToEven); + var cel = Temperature.FromDegreesCelsius(celTmp); + var fah = Math.Round(cel.DegreesFahrenheit, 2, MidpointRounding.ToEven); embedDesc += $"🌡 {cel} is equal to {fah}F.\n"; } @@ -299,10 +306,10 @@ public async Task convert([Remainder] string input = "") { okInt++; - double miTmp = Convert.ToDouble(Regex.Match(input, @"\d+(?=mi)").Value); + var miTmp = System.Convert.ToDouble(Regex.Match(input, @"\d+(?=mi)").Value); - Length mi = Length.FromMiles(miTmp); - double km = Math.Round(mi.Kilometers, 3, MidpointRounding.ToEven); + var mi = Length.FromMiles(miTmp); + var km = Math.Round(mi.Kilometers, 3, MidpointRounding.ToEven); embedDesc += $"📏 {mi} is equal to {km}Km.\n"; } @@ -311,10 +318,10 @@ public async Task convert([Remainder] string input = "") { okInt++; - double kmTmp = Convert.ToDouble(Regex.Match(input, @"\d+(?=km)").Value); + var kmTmp = System.Convert.ToDouble(Regex.Match(input, @"\d+(?=km)").Value); - Length km = Length.FromKilometers(kmTmp); - double mi = Math.Round(km.Miles, 3, MidpointRounding.ToEven); + var km = Length.FromKilometers(kmTmp); + var mi = Math.Round(km.Miles, 3, MidpointRounding.ToEven); embedDesc += $"📏 {km} is equal to {mi}mi.\n"; } @@ -323,11 +330,10 @@ public async Task convert([Remainder] string input = "") { okInt++; - double kgTmp = Convert.ToDouble(Regex.Match(input, @"\d+(?=kg)").Value); - - - Mass kg = Mass.FromKilograms(kgTmp); - double lb = Math.Round(kg.Pounds, 3, MidpointRounding.ToEven); + var kgTmp = System.Convert.ToDouble(Regex.Match(input, @"\d+(?=kg)").Value); + + var kg = Mass.FromKilograms(kgTmp); + var lb = Math.Round(kg.Pounds, 3, MidpointRounding.ToEven); embedDesc += $"⚖️ {kg} is equal to {lb}lbs.\n"; } @@ -336,10 +342,10 @@ public async Task convert([Remainder] string input = "") { okInt++; - double lbTmp = Convert.ToDouble(Regex.Match(input, @"\d+(?=lbs)").Value); + var lbTmp = System.Convert.ToDouble(Regex.Match(input, @"\d+(?=lbs)").Value); - Mass lb = Mass.FromPounds(lbTmp); - double kg = Math.Round(lb.Kilograms, 3, MidpointRounding.ToEven); + var lb = Mass.FromPounds(lbTmp); + var kg = Math.Round(lb.Kilograms, 3, MidpointRounding.ToEven); embedDesc += $"⚖️ {lb} is equal to {kg}Kg.\n"; } @@ -348,31 +354,29 @@ public async Task convert([Remainder] string input = "") { embedDesc += $"No unit has been entered, or it was not recognized. Available units are mi<->km, °C<->F, and kg<->lbs."; } - - - EmbedBuilder builder = new EmbedBuilder() + + var builder = new EmbedBuilder() { Title = "Conversion", Description = embedDesc, Color = EMBED_COLOR }; - await Context.Channel.SendMessageAsync("", false, builder.Build()); + await Context.Channel.SendMessageAsync(string.Empty, false, builder.Build()); } [Command("about")] [Summary("Information about the bot")] public async Task About() { - EmbedBuilder embed = new EmbedBuilder(); + var embed = new EmbedBuilder(); - uint colour = (uint)new Random().Next(0x1000000); // generate random color + var colour = (uint) new Random().Next(0x1000000); // Generate random color - embed.WithDescription( - "This discord bot was created by bealsbe on github! (https://github.com/bealsbe/Floofbot)") - .WithColor(new Discord.Color(colour)); + embed.WithDescription("This discord bot was created by bealsbe on github! (https://github.com/bealsbe/Floofbot)") + .WithColor(new Color(colour)); - await Context.Channel.SendMessageAsync("", false, embed.Build()); + await Context.Channel.SendMessageAsync(string.Empty, false, embed.Build()); } } } diff --git a/Floofbot/Modules/WelcomeGateConfig.cs b/Floofbot/Modules/WelcomeGateConfig.cs index 25aa2efd..213358ae 100644 --- a/Floofbot/Modules/WelcomeGateConfig.cs +++ b/Floofbot/Modules/WelcomeGateConfig.cs @@ -23,101 +23,104 @@ public WelcomeGateConfig(FloofDataContext floofDB) { _floofDB = floofDB; } - private WelcomeGate GetServerConfig(ulong server) + + private async Task GetServerConfigAsync(ulong server) { - // checks if server exists in database and adds if not + // Checks if server exists in database and adds if not var serverConfig = _floofDB.WelcomeGateConfigs.Find(server); - if (serverConfig == null) - { - _floofDB.Add(new WelcomeGate - { - GuildID = server, - Toggle = false, - RoleId = null - }) ; - _floofDB.SaveChanges(); - return _floofDB.WelcomeGateConfigs.Find(server); - } - else + + if (serverConfig != null) return serverConfig; + + _floofDB.Add(new WelcomeGate { - return serverConfig; - } + GuildID = server, + Toggle = false, + RoleId = null + }) ; + + await _floofDB.SaveChangesAsync(); + + return await _floofDB.WelcomeGateConfigs.FindAsync(server); + } - private Discord.Color GenerateColor() + + private Color GenerateColor() { - return new Discord.Color((uint)new Random().Next(0x1000000)); + return new Color((uint)new Random().Next(0x1000000)); } [Command("toggle")] [Summary("Toggles the welcome gate role assignment")] public async Task Toggle() { - - // try toggling + // Try toggling try { - // check the status of logger - var ServerConfig = GetServerConfig(Context.Guild.Id); + // Check the status of logger + var serverConfig = await GetServerConfigAsync(Context.Guild.Id); - ServerConfig.Toggle = !ServerConfig.Toggle; - _floofDB.SaveChanges(); - await Context.Channel.SendMessageAsync("Welcome gate role assignment " + (ServerConfig.Toggle ? "Enabled!" : "Disabled!")); + serverConfig.Toggle = !serverConfig.Toggle; + + await _floofDB.SaveChangesAsync(); + + await Context.Channel.SendMessageAsync("Welcome gate role assignment " + (serverConfig.Toggle ? "Enabled!" : "Disabled!")); } - catch (Exception ex) + catch (Exception e) { - await Context.Channel.SendMessageAsync("An error occured: " + ex.Message); - Log.Error("Error when trying to toggle welcome gate role assignment: " + ex); - return; + await Context.Channel.SendMessageAsync("An error occured: " + e.Message); + Log.Error("Error when trying to toggle welcome gate role assignment: " + e); } } + [Command("role")] [Summary("The role to add to the user")] public async Task SetRole(string roleName = null) { if (roleName == null) { - await Context.Channel.SendMessageAsync("", false, new EmbedBuilder { Description = $"💾 Usage: `welcomegateconfig [rolename]`", Color = GenerateColor() }.Build()); + await Context.Channel.SendMessageAsync(string.Empty, false, new EmbedBuilder { Description = $"💾 Usage: `welcomegateconfig [rolename]`", Color = GenerateColor() }.Build()); return; } - var ServerConfig = GetServerConfig(Context.Guild.Id); - bool roleFound = false; + var serverConfig = await GetServerConfigAsync(Context.Guild.Id); + var roleFound = false; ulong? roleId = null; + try { foreach (SocketRole r in Context.Guild.Roles) { - if (r.Name.ToLower() == roleName.ToLower()) + if (r.Name.ToLower() != roleName.ToLower()) continue; + + if (roleFound == false) // Ok we found 1 role thats GOOD + { + roleFound = true; + roleId = r.Id; + } + else // There is more than 1 role with the same name! { - if (roleFound == false) // ok we found 1 role thats GOOD - { - roleFound = true; - roleId = r.Id; - } - else // there is more than 1 role with the same name! - { - await Context.Channel.SendMessageAsync("More than one role exists with that name! Not sure what to do! Please resolve this. Aborting.."); - return; - } + await Context.Channel.SendMessageAsync("More than one role exists with that name! Not sure what to do! Please resolve this. Aborting.."); + return; } } if (roleFound) { - ServerConfig.RoleId = roleId; - _floofDB.SaveChanges(); + serverConfig.RoleId = roleId; + + await _floofDB.SaveChangesAsync(); + await Context.Channel.SendMessageAsync("Role set!"); } else { await Context.Channel.SendMessageAsync("Unable to find that role. Role not set."); - return; } } - catch (Exception ex) + catch (Exception e) { - await Context.Channel.SendMessageAsync("An error occured: " + ex.Message); - Log.Error("Error when trying to set the welcome gate role: " + ex); + await Context.Channel.SendMessageAsync("An error occured: " + e.Message); + Log.Error("Error when trying to set the welcome gate role: " + e); } } } diff --git a/Floofbot/Program.cs b/Floofbot/Program.cs index ab2d263f..c184434d 100644 --- a/Floofbot/Program.cs +++ b/Floofbot/Program.cs @@ -71,9 +71,9 @@ public async Task MainAsync(string token) await _client.LoginAsync(TokenType.Bot, token); await _client.StartAsync(); } - catch (Exception ex) + catch (Exception e) { - Log.Error(ex.Message); + Log.Error(e.Message); Console.WriteLine("Press any key to exit"); Console.ReadKey(); Environment.Exit(1); diff --git a/Floofbot/Services/BackupService.cs b/Floofbot/Services/BackupService.cs index 759f9d95..e43f4d72 100644 --- a/Floofbot/Services/BackupService.cs +++ b/Floofbot/Services/BackupService.cs @@ -9,7 +9,8 @@ namespace Floofbot.Services { class BackupService { - private TimeSpan backupTime = new TimeSpan(2, 0, 0); + private TimeSpan _backupTime = new TimeSpan(2, 0, 0); + public void Start() { if (string.IsNullOrEmpty(BotConfigFactory.Config.BackupOutputPath) || string.IsNullOrEmpty(BotConfigFactory.Config.BackupScript)) @@ -17,24 +18,27 @@ public void Start() Log.Error("Backups not properly configured in the config file. Backups will not be taken for this session."); return; } - else - { - Log.Information("Automatic backups enabled! Backups will be saved to " + BotConfigFactory.Config.BackupOutputPath + " at " + backupTime); - } + + Log.Information("Automatic backups enabled! Backups will be saved to " + BotConfigFactory.Config.BackupOutputPath + " at " + _backupTime); RunBackups(); } - public async void RunBackups() + + private async void RunBackups() { while (true) { - double targetDelay = backupTime.TotalSeconds - DateTime.UtcNow.TimeOfDay.TotalSeconds; + var targetDelay = _backupTime.TotalSeconds - DateTime.UtcNow.TimeOfDay.TotalSeconds; + if (targetDelay < 0) { targetDelay += 86400; } + await Task.Delay((int)targetDelay * 1000); - System.Diagnostics.Process backupProcess = new System.Diagnostics.Process(); + + var backupProcess = new System.Diagnostics.Process(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { backupProcess.StartInfo.FileName = "powershell.exe"; @@ -53,16 +57,22 @@ public async void RunBackups() + BotConfigFactory.Config.DbPath + " " + BotConfigFactory.Config.BackupOutputPath + " " + BotConfigFactory.Config.NumberOfBackups; //arguments + backupProcess.StartInfo.UseShellExecute = false; backupProcess.StartInfo.RedirectStandardOutput = true; backupProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; backupProcess.StartInfo.CreateNoWindow = true; //not diplay a windows + try { backupProcess.Start(); - string output = backupProcess.StandardOutput.ReadToEnd(); //The output result + + var output = await backupProcess.StandardOutput.ReadToEndAsync(); //The output result + backupProcess.WaitForExit(); + Log.Information(output); + if (backupProcess.ExitCode != 0) { Log.Error("Backup script failed. Process returned exit code " + backupProcess.ExitCode); diff --git a/Floofbot/Services/EventHandlerService.cs b/Floofbot/Services/EventHandlerService.cs index 1fca4e80..d86ad2f4 100644 --- a/Floofbot/Services/EventHandlerService.cs +++ b/Floofbot/Services/EventHandlerService.cs @@ -9,151 +9,165 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; namespace Floofbot.Services { public class EventHandlerService { - - private DiscordSocketClient _client; private WordFilterService _wordFilterService; private NicknameAlertService _nicknameAlertService; private RaidProtectionService _raidProtectionService; private UserRoleRetentionService _userRoleRetentionService; - private WelcomeGateService _welcomeGateService; private static readonly Color ADMIN_COLOR = Color.DarkOrange; - // list of announcement channels - private List announcementChannels; - - // rules gate config - private Dictionary rulesGateConfig = BotConfigFactory.Config.RulesGate; - + // List of announcement channels + private List _announcementChannels; - public EventHandlerService(DiscordSocketClient client) + // Rules gate config + private Dictionary _rulesGateConfig = BotConfigFactory.Config.RulesGate; + + public EventHandlerService(DiscordSocketClient clientParam) { - _client = client; + // We don't need a global private variable if we ONLY use it in this method! + var client = clientParam; - // assisting handlers + // Assisting handlers _wordFilterService = new WordFilterService(); _nicknameAlertService = new NicknameAlertService(new FloofDataContext()); _raidProtectionService = new RaidProtectionService(); _userRoleRetentionService = new UserRoleRetentionService(new FloofDataContext()); - _welcomeGateService = new WelcomeGateService(new FloofDataContext()); - - // event handlers - _client.MessageUpdated += MessageUpdated; - _client.MessageDeleted += MessageDeleted; - _client.UserBanned += UserBanned; - _client.UserUnbanned += UserUnbanned; - _client.UserJoined += UserJoined; - _client.UserLeft += UserLeft; - _client.GuildMemberUpdated += GuildMemberUpdated; - _client.GuildMemberUpdated += _welcomeGateService.HandleWelcomeGate; // welcome gate handler - _client.UserUpdated += UserUpdated; - _client.MessageReceived += OnMessage; - _client.MessageReceived += RulesGate; // rfurry rules gate - _client.ReactionAdded += _nicknameAlertService.OnReactionAdded; - - // a list of announcement channels for auto publishing - announcementChannels = BotConfigFactory.Config.AnnouncementChannels; + // Same rules as client, it's only used here, so we don't need a global variable for it! + var welcomeGateService = new WelcomeGateService(); + + // Event handlers + client.MessageUpdated += MessageUpdated; + client.MessageDeleted += MessageDeleted; + client.UserBanned += UserBanned; + client.UserUnbanned += UserUnbanned; + client.UserJoined += UserJoined; + client.UserLeft += UserLeft; + client.GuildMemberUpdated += GuildMemberUpdated; + client.GuildMemberUpdated += welcomeGateService.HandleWelcomeGate; // welcome gate handler + client.UserUpdated += UserUpdated; + client.MessageReceived += OnMessage; + client.MessageReceived += RulesGate; // rfurry rules gate + client.ReactionAdded += _nicknameAlertService.OnReactionAdded; + + // A list of announcement channels for auto publishing + _announcementChannels = BotConfigFactory.Config.AnnouncementChannels; } - public async Task PublishAnnouncementMessages(SocketUserMessage msg) + + private async Task PublishAnnouncementMessages(SocketUserMessage msg) { - foreach (ulong chan in announcementChannels) + foreach (var chan in _announcementChannels) { - if (msg.Channel.Id == chan) + if (msg.Channel.Id != chan) continue; + + if (msg.Channel.GetType() == typeof(SocketNewsChannel)) { - if (msg.Channel.GetType() == typeof(SocketNewsChannel)) - { - await msg.CrosspostAsync(); - } + await msg.CrosspostAsync(); } } } - public async Task GetChannel(Discord.IGuild guild, string eventName = null) + + private async Task GetChannel(IGuild guild, string eventName = null) { if (eventName == null) return null; - FloofDataContext _floofDb = new FloofDataContext(); - - var serverConfig = _floofDb.LogConfigs.Find(guild.Id); - if (serverConfig == null) // guild not in database - return null; - System.Reflection.PropertyInfo propertyInfo = serverConfig.GetType().GetProperty(eventName); - ulong logChannel = (ulong)(propertyInfo.GetValue(serverConfig, null)); - var textChannel = await guild.GetTextChannelAsync(logChannel); - return textChannel; + await using (var floofDb = new FloofDataContext()) + { + var serverConfig = await floofDb.LogConfigs.FindAsync(guild.Id); + + if (serverConfig == null) // guild not in database + return null; + + var propertyInfo = serverConfig.GetType().GetProperty(eventName); + var logChannel = (ulong) propertyInfo.GetValue(serverConfig, null); + var textChannel = await guild.GetTextChannelAsync(logChannel); + + return textChannel; + } } - public bool IsToggled(IGuild guild) + private bool IsToggled(IGuild guild) { - // check if the logger is toggled on in this server - // check the status of logger - FloofDataContext _floofDb = new FloofDataContext(); - - var ServerConfig = _floofDb.LogConfigs.Find(guild.Id); - if (ServerConfig == null) // no entry in DB for server - not configured - return false; - - return ServerConfig.IsOn; + using (var floofDb = new FloofDataContext()) + { + // Check if the logger is toggled on in this server + // Check the status of logger + var serverConfig = floofDb.LogConfigs.Find(guild.Id); + + return serverConfig != null && serverConfig.IsOn; + } } + private async Task CheckUserAutoban(IGuildUser user) { - FloofDataContext _floofDb = new FloofDataContext(); - BanOnJoin badUserAutoban = _floofDb.BansOnJoin.AsQueryable().Where(u => u.UserID == user.Id).FirstOrDefault(); - if (badUserAutoban != null) // user is in the list to be autobanned + await using (var floofDb = new FloofDataContext()) { - //sends message to user - EmbedBuilder builder = new EmbedBuilder(); - builder.Title = "⚖️ Ban Notification"; - builder.Description = $"You have been automatically banned from {user.Guild.Name}"; - builder.AddField("Reason", badUserAutoban.Reason); - builder.Color = ADMIN_COLOR; - await user.SendMessageAsync("", false, builder.Build()); - await user.Guild.AddBanAsync(user.Id, 0, $"{badUserAutoban.ModUsername} -> {badUserAutoban.Reason} (autobanned on user join)"); - - try - { - _floofDb.Remove(badUserAutoban); - await _floofDb.SaveChangesAsync(); - return; - } - catch (Exception ex) // db error + var badUserAutoban = await floofDb.BansOnJoin.AsQueryable().FirstOrDefaultAsync(u => u.UserID == user.Id); + + if (badUserAutoban != null) // user is in the list to be autobanned { - Log.Error("Error with the auto ban on join system: " + ex); - return; + //sends message to user + var builder = new EmbedBuilder + { + Title = "⚖️ Ban Notification", + Description = $"You have been automatically banned from {user.Guild.Name}", + Color = ADMIN_COLOR + }.AddField("Reason", badUserAutoban.Reason); + + await user.SendMessageAsync(string.Empty, false, builder.Build()); + + await user.Guild.AddBanAsync(user.Id, 0, + $"{badUserAutoban.ModUsername} -> {badUserAutoban.Reason} (autobanned on user join)"); + + try + { + floofDb.Remove(badUserAutoban); + + await floofDb.SaveChangesAsync(); + } + catch (Exception e) // db error + { + Log.Error("Error with the auto ban on join system: " + e); + } } } } - // rfurry rules gate - public async Task RulesGate(SocketMessage msg) + // r/Furry rules gate + private async Task RulesGate(SocketMessage msg) { var userMsg = msg as SocketUserMessage; + if (msg == null || msg.Author.IsBot) return; - // rules gate info - ulong rulesChannelId = Convert.ToUInt64(rulesGateConfig["RulesChannel"]); - ulong readRulesRoleId = Convert.ToUInt64(rulesGateConfig["RulesRole"]); - string rulesBypassString = rulesGateConfig["Keyword"]; + // Rules gate info + var rulesChannelId = Convert.ToUInt64(_rulesGateConfig["RulesChannel"]); + var readRulesRoleId = Convert.ToUInt64(_rulesGateConfig["RulesRole"]); + var rulesBypassString = _rulesGateConfig["Keyword"]; if (msg.Channel.Id == rulesChannelId && userMsg.Content.ToLower().Contains(rulesBypassString)) { var user = (IGuildUser)msg.Author; + await user.AddRoleAsync(user.Guild.GetRole(readRulesRoleId)); await userMsg.DeleteAsync(); } } - public Task OnMessage(SocketMessage msg) + + private Task OnMessage(SocketMessage msg) { if (msg.Channel.GetType() == typeof(SocketDMChannel)) return Task.CompletedTask; var userMsg = msg as SocketUserMessage; - var _ = Task.Run(async () => + + Task.Run(async () => { try { @@ -162,71 +176,78 @@ public Task OnMessage(SocketMessage msg) if (msg == null || msg.Author.IsBot) return; - var channel = msg.Channel as ITextChannel; - bool messageTriggeredRaidProtection = _raidProtectionService.CheckMessage(new FloofDataContext(), msg).Result; + var messageTriggeredRaidProtection = _raidProtectionService.CheckMessage(new FloofDataContext(), msg).Result; + if (messageTriggeredRaidProtection) { await msg.DeleteAsync(); return; } - string randomResponse = RandomResponseGenerator.GenerateResponse(userMsg); + var randomResponse = RandomResponseGenerator.GenerateResponse(userMsg); + if (!string.IsNullOrEmpty(randomResponse)) { await msg.Channel.SendMessageAsync(randomResponse); - return; } } - catch (Exception ex) + catch (Exception e) { - Log.Error("Error with the on message event handler: " + ex); - return; + Log.Error("Error with the on message event handler: " + e); } }); return Task.CompletedTask; } - public Task MessageUpdated(Cacheable before, SocketMessage after, ISocketMessageChannel chan) + + private Task MessageUpdated(Cacheable before, SocketMessage after, ISocketMessageChannel chan) { - var _ = Task.Run(async () => + Task.Run(async () => { try { - // deal with empty message + // Deal with empty message var messageBefore = (before.HasValue ? before.Value : null) as IUserMessage; + if (messageBefore == null) return; if (after.Author.IsBot) return; - var channel = chan as ITextChannel; // channel null, dm message? + var channel = chan as ITextChannel; // Channel null, DM message? + if (channel == null) return; if (messageBefore.Content == after.Content) // no change return; - bool messageTriggeredRaidProtection = _raidProtectionService.CheckMessage(new FloofDataContext(), after).Result; + var messageTriggeredRaidProtection = _raidProtectionService.CheckMessage(new FloofDataContext(), after).Result; + if (messageTriggeredRaidProtection) { await after.DeleteAsync(); return; } - bool hasBadWord = _wordFilterService.hasFilteredWord(new FloofDataContext(), after.Content, channel.Guild.Id, after.Channel.Id); + var hasBadWord = _wordFilterService.HasFilteredWord(new FloofDataContext(), after.Content, channel.Guild.Id, after.Channel.Id); + if (hasBadWord) { await after.DeleteAsync(); + var botMsg = await after.Channel.SendMessageAsync($"{after.Author.Mention} There was a filtered word in that message. Please be mindful of your language!"); + await Task.Delay(5000); await botMsg.DeleteAsync(); } - if ((IsToggled(channel.Guild)) == false) // not toggled on + if (IsToggled(channel.Guild) == false) // Not toggled on return; - Discord.ITextChannel logChannel = await GetChannel(channel.Guild, "MessageUpdatedChannel"); + var logChannel = await GetChannel(channel.Guild, "MessageUpdatedChannel"); + if (logChannel == null) return; @@ -243,207 +264,206 @@ public Task MessageUpdated(Cacheable before, SocketMessage afte if (Uri.IsWellFormedUriString(after.Author.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(after.Author.GetAvatarUrl()); - await logChannel.SendMessageAsync("", false, embed.Build()); + await logChannel.SendMessageAsync(string.Empty, false, embed.Build()); } - catch (Exception ex) + catch (Exception e) { - Log.Error("Error with the message updated event handler: " + ex); - return; + Log.Error("Error with the message updated event handler: " + e); } }); + return Task.CompletedTask; } - public Task MessageDeleted(Cacheable before, ISocketMessageChannel chan) + + private Task MessageDeleted(Cacheable before, ISocketMessageChannel chan) { - var _ = Task.Run(async () => + Task.Run(async () => { try { // deal with empty message var message = (before.HasValue ? before.Value : null) as IUserMessage; + if (message == null) return; if (message.Author.IsBot) return; - var channel = chan as ITextChannel; // channel null, dm message? + var channel = chan as ITextChannel; // Channel null, DM message? + if (channel == null) return; - if ((IsToggled(channel.Guild)) == false) // not toggled on + if (IsToggled(channel.Guild) == false) // not toggled on return; - - - Discord.ITextChannel logChannel = await GetChannel(channel.Guild, "MessageDeletedChannel"); + + var logChannel = await GetChannel(channel.Guild, "MessageDeletedChannel"); + if (logChannel == null) return; - var embed = new EmbedBuilder(); - - embed.WithTitle($"⚠️ Message Deleted | {message.Author.Username}#{message.Author.Discriminator}") - .WithColor(Color.Gold) - .WithDescription($"{message.Author.Mention} ({message.Author.Id}) has had their message deleted in {channel.Mention}!") - .WithCurrentTimestamp() - .WithFooter($"user_message_deleted user_messagelog {message.Author.Id}"); - if (message.Content.Length > 0) - { + var embed = new EmbedBuilder() + .WithTitle($"⚠️ Message Deleted | {message.Author.Username}#{message.Author.Discriminator}") + .WithColor(Color.Gold) + .WithDescription($"{message.Author.Mention} ({message.Author.Id}) has had their message deleted in {channel.Mention}!") + .WithCurrentTimestamp() + .WithFooter($"user_message_deleted user_messagelog {message.Author.Id}"); + + if (message.Content.Length > 0) embed.AddField("Content", message.Content); - } + if (message.Attachments.Count > 0) - { embed.AddField("Attachments", String.Join("\n", message.Attachments.Select(it => it.Url))); - } if (Uri.IsWellFormedUriString(message.Author.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(message.Author.GetAvatarUrl()); - await logChannel.SendMessageAsync("", false, embed.Build()); + await logChannel.SendMessageAsync(string.Empty, false, embed.Build()); } - catch (Exception ex) + catch (Exception e) { - Log.Error("Error with the message deleted event handler: " + ex); - return; + Log.Error("Error with the message deleted event handler: " + e); } }); + return Task.CompletedTask; } + public Task MessageDeletedByBot(SocketMessage before, ITextChannel channel, string reason = "N/A") { - var _ = Task.Run(async () => + Task.Run(async () => { try { if (before.Author.IsBot) return; - // deal with empty message + // Deal with empty message if (before.Content == null) return; if (channel == null) return; - if ((IsToggled(channel.Guild)) == false) // not toggled on + if (IsToggled(channel.Guild) == false) // not toggled on return; - Discord.ITextChannel logChannel = await GetChannel(channel.Guild, "MessageDeletedChannel"); + var logChannel = await GetChannel(channel.Guild, "MessageDeletedChannel"); + if (logChannel == null) return; - var embed = new EmbedBuilder(); - - embed.WithTitle($"⚠️ Message Deleted By Bot | {before.Author.Username}#{before.Author.Discriminator}") - .WithColor(Color.Gold) - .WithDescription($"{before.Author.Mention} ({before.Author.Id}) has had their message deleted in {channel.Mention}!") - .AddField("Content", before.Content) - .AddField("Reason", reason) - .WithFooter($"user_message_bot_delete user_messagelog {before.Author.Id}") - .WithCurrentTimestamp(); + var embed = new EmbedBuilder() + .WithTitle($"⚠️ Message Deleted By Bot | {before.Author.Username}#{before.Author.Discriminator}") + .WithColor(Color.Gold) + .WithDescription($"{before.Author.Mention} ({before.Author.Id}) has had their message deleted in {channel.Mention}!") + .AddField("Content", before.Content) + .AddField("Reason", reason) + .WithFooter($"user_message_bot_delete user_messagelog {before.Author.Id}") + .WithCurrentTimestamp(); if (Uri.IsWellFormedUriString(before.Author.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(before.Author.GetAvatarUrl()); - await logChannel.SendMessageAsync("", false, embed.Build()); + await logChannel.SendMessageAsync(string.Empty, false, embed.Build()); } - catch (Exception ex) + catch (Exception e) { - Log.Error("Error with the message deleted by bot event handler: " + ex); - return; + Log.Error("Error with the message deleted by bot event handler: " + e); } }); + return Task.CompletedTask; } - public Task UserBanned(IUser user, IGuild guild) + + private Task UserBanned(IUser user, IGuild guild) { - var _ = Task.Run(async () => + Task.Run(async () => { try { if (user.IsBot) return; - if ((IsToggled(guild)) == false) + if (IsToggled(guild) == false) return; - Discord.ITextChannel channel = await GetChannel(guild, "UserBannedChannel"); + var channel = await GetChannel(guild, "UserBannedChannel"); + if (channel == null) return; var banReason = guild.GetBanAsync(user.Id).Result.Reason; - var embed = new EmbedBuilder(); - - embed.WithTitle($"🔨 User Banned | {user.Username}#{user.Discriminator}") - .WithColor(Color.Red) - .WithDescription($"{user.Mention} | ``{user.Id}``") - .WithFooter($"user_banned user_banlog {user.Id}") - .WithCurrentTimestamp(); + var embed = new EmbedBuilder() + .WithTitle($"🔨 User Banned | {user.Username}#{user.Discriminator}") + .WithColor(Color.Red) + .WithDescription($"{user.Mention} | ``{user.Id}``") + .WithFooter($"user_banned user_banlog {user.Id}") + .WithCurrentTimestamp(); - if (banReason == null) - embed.AddField("Reason", "No Reason Provided"); - else - embed.AddField("Reason", banReason); + embed.AddField("Reason", banReason ?? "No Reason Provided"); if (Uri.IsWellFormedUriString(user.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(user.GetAvatarUrl()); - await channel.SendMessageAsync("", false, embed.Build()); + await channel.SendMessageAsync(string.Empty, false, embed.Build()); } - catch (Exception ex) + catch (Exception e) { - Log.Error("Error with the user banned event handler: " + ex); - return; + Log.Error("Error with the user banned event handler: " + e); } }); + return Task.CompletedTask; - } - public Task UserUnbanned(IUser user, IGuild guild) + + private Task UserUnbanned(IUser user, IGuild guild) { - var _ = Task.Run(async () => + Task.Run(async () => { try { if (user.IsBot) return; - if ((IsToggled(guild)) == false) + if (IsToggled(guild) == false) return; - Discord.ITextChannel channel = await GetChannel(guild, "UserUnbannedChannel"); + var channel = await GetChannel(guild, "UserUnbannedChannel"); + if (channel == null) return; - var embed = new EmbedBuilder(); - - embed.WithTitle($"♻️ User Unbanned | {user.Username}#{user.Discriminator}") - .WithColor(Color.Gold) - .WithDescription($"{user.Mention} | ``{user.Id}``") - .WithFooter($"user_unbanned user_banlog {user.Id}") - .WithCurrentTimestamp(); + var embed = new EmbedBuilder() + .WithTitle($"♻️ User Unbanned | {user.Username}#{user.Discriminator}") + .WithColor(Color.Gold) + .WithDescription($"{user.Mention} | ``{user.Id}``") + .WithFooter($"user_unbanned user_banlog {user.Id}") + .WithCurrentTimestamp(); if (Uri.IsWellFormedUriString(user.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(user.GetAvatarUrl()); - await channel.SendMessageAsync("", false, embed.Build()); + await channel.SendMessageAsync(string.Empty, false, embed.Build()); } - catch (Exception ex) + catch (Exception e) { - Log.Error("Error with the user unbanned event handler: " + ex); - return; + Log.Error("Error with the user unbanned event handler: " + e); } }); + return Task.CompletedTask; - } - public Task UserJoined(IGuildUser user) + + private Task UserJoined(IGuildUser user) { - var _ = Task.Run(async () => + Task.Run(async () => { try { - await CheckUserAutoban(user); // check if the user is to be automatically banned on join + await CheckUserAutoban(user); // Check if the user is to be automatically banned on join if (user.IsBot) return; @@ -451,40 +471,40 @@ public Task UserJoined(IGuildUser user) await _raidProtectionService.CheckForExcessiveJoins(user.Guild); await _userRoleRetentionService.RestoreUserRoles(user); - if ((IsToggled(user.Guild)) == false) + if (IsToggled(user.Guild) == false) return; - Discord.ITextChannel channel = await GetChannel(user.Guild, "UserJoinedChannel"); + var channel = await GetChannel(user.Guild, "UserJoinedChannel"); + if (channel == null) return; - - var embed = new EmbedBuilder(); - - embed.WithTitle($"✅ User Joined | {user.Username}#{user.Discriminator}") - .WithColor(Color.Green) - .WithDescription($"{user.Mention} | ``{user.Id}``") - .AddField("Joined Server", user.JoinedAt?.ToString("ddd, dd MMM yyyy"), true) - .AddField("Joined Discord", user.CreatedAt.ToString("ddd, dd MMM yyyy"), true) - .WithFooter($"user_join user_joinlog {user.Id}") - .WithCurrentTimestamp(); + var embed = new EmbedBuilder() + .WithTitle($"✅ User Joined | {user.Username}#{user.Discriminator}") + .WithColor(Color.Green) + .WithDescription($"{user.Mention} | ``{user.Id}``") + .AddField("Joined Server", user.JoinedAt?.ToString("ddd, dd MMM yyyy"), true) + .AddField("Joined Discord", user.CreatedAt.ToString("ddd, dd MMM yyyy"), true) + .WithFooter($"user_join user_joinlog {user.Id}") + .WithCurrentTimestamp(); if (Uri.IsWellFormedUriString(user.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(user.GetAvatarUrl()); - await channel.SendMessageAsync("", false, embed.Build()); + await channel.SendMessageAsync(string.Empty, false, embed.Build()); } - catch (Exception ex) + catch (Exception e) { - Log.Error("Error with the user joined event handler: " + ex); - return; + Log.Error("Error with the user joined event handler: " + e); } }); + return Task.CompletedTask; } - public Task UserLeft(IGuildUser user) + + private Task UserLeft(IGuildUser user) { - var _ = Task.Run(async () => + Task.Run(async () => { try { @@ -493,53 +513,55 @@ public Task UserLeft(IGuildUser user) await _userRoleRetentionService.LogUserRoles(user); - if ((IsToggled(user.Guild)) == false) + if (IsToggled(user.Guild) == false) return; - Discord.ITextChannel channel = await GetChannel(user.Guild, "UserLeftChannel"); + var channel = await GetChannel(user.Guild, "UserLeftChannel"); if (channel == null) return; - var embed = new EmbedBuilder(); - embed.WithTitle($"❌ User Left | {user.Username}#{user.Discriminator}") - .WithColor(Color.Red) - .WithDescription($"{user.Mention} | ``{user.Id}``"); + var embed = new EmbedBuilder() + .WithTitle($"❌ User Left | {user.Username}#{user.Discriminator}") + .WithColor(Color.Red) + .WithDescription($"{user.Mention} | ``{user.Id}``"); + if (user.JoinedAt != null) { - DateTimeOffset userJoined = ((DateTimeOffset)user.JoinedAt); - TimeSpan interval = DateTime.UtcNow - userJoined.DateTime; - string day_word = interval.Days == 1 ? "day" : "days"; + var userJoined = (DateTimeOffset)user.JoinedAt; + var interval = DateTime.UtcNow - userJoined.DateTime; + var dayWord = interval.Days == 1 ? "day" : "days"; + embed.AddField("Joined Server", userJoined.ToString("ddd, dd MMM yyyy"), true); - embed.AddField("Time at Server", $"{interval.Days} {day_word}", true); + embed.AddField("Time at Server", $"{interval.Days} {dayWord}", true); } + embed.WithFooter($"user_leave user_joinlog {user.Id}") .WithCurrentTimestamp(); if (Uri.IsWellFormedUriString(user.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(user.GetAvatarUrl()); - await channel.SendMessageAsync("", false, embed.Build()); + await channel.SendMessageAsync(string.Empty, false, embed.Build()); } - catch (Exception ex) + catch (Exception e) { - Log.Error("Error with the user left event handler: " + ex); - return; + Log.Error("Error with the user left event handler: " + e); } }); + return Task.CompletedTask; - } - public Task UserUpdated(SocketUser before, SocketUser userAfter) - { - var _ = Task.Run(async () => + private Task UserUpdated(SocketUser before, SocketUser userAfter) + { + Task.Run(async () => { try { if (!(userAfter is SocketGuildUser after)) return; - if (before == null || after == null) // empty user params + if (before == null || after == null) // Empty user params return; if (after.IsBot) @@ -547,7 +569,8 @@ public Task UserUpdated(SocketUser before, SocketUser userAfter) if (before.Username != after.Username) { - List badWords = _wordFilterService.filteredWordsInName(new FloofDataContext(), after.Username, after.Guild.Id); + var badWords = _wordFilterService.FilteredWordsInName(new FloofDataContext(), after.Username, after.Guild.Id); + if (badWords != null) await _nicknameAlertService.HandleBadNickname(after, after.Guild, badWords); } @@ -555,7 +578,8 @@ public Task UserUpdated(SocketUser before, SocketUser userAfter) if (IsToggled(after.Guild) == false) // turned off return; - Discord.ITextChannel channel = await GetChannel(after.Guild, "MemberUpdatesChannel"); + var channel = await GetChannel(after.Guild, "MemberUpdatesChannel"); + if (channel == null) // no log channel set return; @@ -573,7 +597,6 @@ public Task UserUpdated(SocketUser before, SocketUser userAfter) if (Uri.IsWellFormedUriString(after.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(after.GetAvatarUrl()); - } else if (before.AvatarId != after.AvatarId) { @@ -582,8 +605,10 @@ public Task UserUpdated(SocketUser before, SocketUser userAfter) .WithDescription($"{after.Mention} | ``{after.Id}``") .WithFooter($"user_avatar_change {after.Id}") .WithCurrentTimestamp(); + if (Uri.IsWellFormedUriString(before.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(before.GetAvatarUrl()); + if (Uri.IsWellFormedUriString(after.GetAvatarUrl(), UriKind.Absolute)) embed.WithImageUrl(after.GetAvatarUrl()); } @@ -591,21 +616,20 @@ public Task UserUpdated(SocketUser before, SocketUser userAfter) { return; } - await channel.SendMessageAsync("", false, embed.Build()); + await channel.SendMessageAsync(string.Empty, false, embed.Build()); } - catch (Exception ex) + catch (Exception e) { - Log.Error("Error with the user updated event handler: " + ex); - return; + Log.Error("Error with the user updated event handler: " + e); } }); + return Task.CompletedTask; - } - public Task GuildMemberUpdated(SocketGuildUser before, SocketGuildUser after) + private Task GuildMemberUpdated(SocketGuildUser before, SocketGuildUser after) { - var _ = Task.Run(async () => + Task.Run(async () => { try { @@ -614,11 +638,11 @@ public Task GuildMemberUpdated(SocketGuildUser before, SocketGuildUser after) if (after.IsBot) return; - - - if (after.Nickname != null && (after.Nickname != before.Nickname)) + + if (after.Nickname != null && after.Nickname != before.Nickname) { - List badWords = _wordFilterService.filteredWordsInName(new FloofDataContext(), after.Nickname, after.Guild.Id); + var badWords = _wordFilterService.FilteredWordsInName(new FloofDataContext(), after.Nickname, after.Guild.Id); + if (badWords != null) await _nicknameAlertService.HandleBadNickname(after, after.Guild, badWords); } @@ -626,7 +650,8 @@ public Task GuildMemberUpdated(SocketGuildUser before, SocketGuildUser after) if (IsToggled(after.Guild) == false) // turned off return; - Discord.ITextChannel channel = await GetChannel(after.Guild, "MemberUpdatesChannel"); + var channel = await GetChannel(after.Guild, "MemberUpdatesChannel"); + if (channel == null) // no log channel set return; @@ -646,30 +671,34 @@ public Task GuildMemberUpdated(SocketGuildUser before, SocketGuildUser after) embed.AddField("New Nickname", after.Nickname, true); } else if (after.Nickname == null) // removed their nickname + { embed.AddField("Old Nickname", before.Nickname, true); + } else // new nickname, didnt have one before + { embed.AddField("New Nickname", after.Nickname, true); + } if (Uri.IsWellFormedUriString(after.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(after.GetAvatarUrl()); - } else if (before.Roles.Count != after.Roles.Count) { - List beforeRoles = new List(before.Roles); - List afterRoles = new List(after.Roles); - List roleDifference = new List(); + var beforeRoles = new List(before.Roles); + var afterRoles = new List(after.Roles); + var roleDifference = new List(); if (before.Roles.Count > after.Roles.Count) // roles removed { roleDifference = beforeRoles.Except(afterRoles).ToList(); + embed.WithTitle($"❗ Roles Removed | {after.Username}#{after.Discriminator}") .WithColor(Color.Orange) .WithDescription($"{after.Mention} | ``{after.Id}``") .WithFooter($"user_roles_removed user_rolelog {after.Id}") .WithCurrentTimestamp(); - foreach (SocketRole role in roleDifference) + foreach (var role in roleDifference) { embed.AddField("Role Removed", role); } @@ -680,15 +709,18 @@ public Task GuildMemberUpdated(SocketGuildUser before, SocketGuildUser after) else if (before.Roles.Count < after.Roles.Count) // roles added { roleDifference = afterRoles.Except(beforeRoles).ToList(); + embed.WithTitle($"❗ Roles Added | {after.Username}#{after.Discriminator}") .WithColor(Color.Orange) .WithDescription($"{after.Mention} | ``{after.Id}``") .WithFooter($"user_roles_added user_rolelog {after.Id}") .WithCurrentTimestamp(); - foreach (SocketRole role in roleDifference) + + foreach (var role in roleDifference) { embed.AddField("Role Added", role); } + if (Uri.IsWellFormedUriString(after.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(after.GetAvatarUrl()); } @@ -697,130 +729,131 @@ public Task GuildMemberUpdated(SocketGuildUser before, SocketGuildUser after) { return; } - await channel.SendMessageAsync("", false, embed.Build()); + + await channel.SendMessageAsync(string.Empty, false, embed.Build()); } - catch (Exception ex) + catch (Exception e) { - Log.Error("Error with the guild member updated event handler: " + ex); - return; + Log.Error("Error with the guild member updated event handler: " + e); } }); + return Task.CompletedTask; - } + public Task UserKicked(IUser user, IUser kicker, IGuild guild) { - var _ = Task.Run(async () => + Task.Run(async () => { try { if (user.IsBot) return; - if ((IsToggled(guild)) == false) + if (IsToggled(guild) == false) return; - Discord.ITextChannel channel = await GetChannel(guild, "UserKickedChannel"); + var channel = await GetChannel(guild, "UserKickedChannel"); + if (channel == null) return; - var embed = new EmbedBuilder(); - - embed.WithTitle($"👢 User Kicked | {user.Username}#{user.Discriminator}") - .WithColor(Color.Red) - .WithDescription($"{user.Mention} | ``{user.Id}``") - .AddField("Kicked By", kicker.Mention) - .WithFooter($"user_kicked {user.Id}") - .WithCurrentTimestamp(); + var embed = new EmbedBuilder() + .WithTitle($"👢 User Kicked | {user.Username}#{user.Discriminator}") + .WithColor(Color.Red) + .WithDescription($"{user.Mention} | ``{user.Id}``") + .AddField("Kicked By", kicker.Mention) + .WithFooter($"user_kicked {user.Id}") + .WithCurrentTimestamp(); if (Uri.IsWellFormedUriString(user.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(user.GetAvatarUrl()); - await channel.SendMessageAsync("", false, embed.Build()); + await channel.SendMessageAsync(string.Empty, false, embed.Build()); } - catch (Exception ex) + catch (Exception e) { - Log.Error("Error with the user kicked event handler: " + ex); - return; + Log.Error("Error with the user kicked event handler: " + e); } }); + return Task.CompletedTask; } public Task UserMuted(IUser user, IUser muter, IGuild guild) { - var _ = Task.Run(async () => + Task.Run(async () => { try { if (user.IsBot) return; - if ((IsToggled(guild)) == false) + + if (IsToggled(guild) == false) return; - Discord.ITextChannel channel = await GetChannel(guild, "UserMutedChannel"); + var channel = await GetChannel(guild, "UserMutedChannel"); if (channel == null) return; - var embed = new EmbedBuilder(); - - embed.WithTitle($"🔇 User Muted | {user.Username}#{user.Discriminator}") - .WithColor(Color.Teal) - .WithDescription($"{user.Mention} | ``{user.Id}``") - .AddField("Muted By", muter.Mention) - .WithFooter($"user_muted user_mutelog {user.Id}") - .WithCurrentTimestamp(); + var embed = new EmbedBuilder() + .WithTitle($"🔇 User Muted | {user.Username}#{user.Discriminator}") + .WithColor(Color.Teal) + .WithDescription($"{user.Mention} | ``{user.Id}``") + .AddField("Muted By", muter.Mention) + .WithFooter($"user_muted user_mutelog {user.Id}") + .WithCurrentTimestamp(); if (Uri.IsWellFormedUriString(user.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(user.GetAvatarUrl()); - await channel.SendMessageAsync("", false, embed.Build()); + await channel.SendMessageAsync(string.Empty, false, embed.Build()); } - catch (Exception ex) + catch (Exception e) { - Log.Error("Error with the user muted event handler: " + ex); - return; + Log.Error("Error with the user muted event handler: " + e); } }); + return Task.CompletedTask; } + public Task UserUnmuted(IUser user, IUser unmuter, IGuild guild) { - var _ = Task.Run(async () => + Task.Run(async () => { try { if (user.IsBot) return; - if ((IsToggled(guild)) == false) + if (IsToggled(guild) == false) return; - Discord.ITextChannel channel = await GetChannel(guild, "UserUnmutedChannel"); + var channel = await GetChannel(guild, "UserUnmutedChannel"); if (channel == null) return; - var embed = new EmbedBuilder(); - - embed.WithTitle($"🔊 User Unmuted | {user.Username}#{user.Discriminator}") - .WithColor(Color.Teal) - .WithDescription($"{user.Mention} | ``{user.Id}``") - .AddField("Unmuted By", unmuter.Mention) - .WithFooter($"user_unmuted user_mutelog {user.Id}") - .WithCurrentTimestamp(); + var embed = new EmbedBuilder() + .WithTitle($"🔊 User Unmuted | {user.Username}#{user.Discriminator}") + .WithColor(Color.Teal) + .WithDescription($"{user.Mention} | ``{user.Id}``") + .AddField("Unmuted By", unmuter.Mention) + .WithFooter($"user_unmuted user_mutelog {user.Id}") + .WithCurrentTimestamp(); if (Uri.IsWellFormedUriString(user.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(user.GetAvatarUrl()); - await channel.SendMessageAsync("", false, embed.Build()); + await channel.SendMessageAsync(string.Empty, false, embed.Build()); } - catch (Exception ex) + catch (Exception e) { - Log.Error("Error with the user unmuted event handler: " + ex); - return; + Log.Error("Error with the user unmuted event handler: " + e); } }); + return Task.CompletedTask; } } diff --git a/Floofbot/Services/NicknameAlertService.cs b/Floofbot/Services/NicknameAlertService.cs index fad63524..a237d357 100644 --- a/Floofbot/Services/NicknameAlertService.cs +++ b/Floofbot/Services/NicknameAlertService.cs @@ -17,7 +17,7 @@ public class NicknameAlertService { private FloofDataContext _floofDb; - private Dictionary alertMessageIdsDic = new Dictionary(); + private Dictionary _alertMessageIdsDic = new Dictionary(); private ITextChannel _channel; private static readonly Emoji BAN_EMOJI = new Emoji("🔨"); private static readonly Emoji WARN_EMOJI = new Emoji("⚠️"); @@ -29,6 +29,7 @@ public NicknameAlertService(FloofDataContext floofDb) { _floofDb = floofDb; } + private async Task GetChannel(IGuild guild, ulong channelId) { return await guild.GetTextChannelAsync(channelId); @@ -42,33 +43,34 @@ public async Task HandleBadNickname(SocketGuildUser badUser, IGuild guild, List< { return; } + _channel = await GetChannel(guild, serverConfig.Channel); var embed = new EmbedBuilder() .WithDescription($"{REMOVE_NICKNAME_EMOJI.Name}: Remove Nickname\n" + - $"{WARN_EMOJI.Name}: Warn\n" + - $"{KICK_EMOJI.Name}: Kick\n" + - $"{BAN_EMOJI.Name}: Ban\n" + - $"{NO_ACTION_EMOJI.Name}: No Action") + $"{WARN_EMOJI.Name}: Warn\n" + + $"{KICK_EMOJI.Name}: Kick\n" + + $"{BAN_EMOJI.Name}: Ban\n" + + $"{NO_ACTION_EMOJI.Name}: No Action") .Build(); var message = await _channel.SendMessageAsync($"{badUser.Mention} ({badUser.Username}#{badUser.Discriminator}) has been " + $"detected with a bad name! What should I do?" + (badUser.Nickname != null ? $"\n\nNickname: {badUser.Nickname}" : $"\n\nUsername: {badUser.Username}#{badUser.Discriminator}") + $"\n\nDetected word(s): **{string.Join(", ", badWords)}**", false, embed); + await message.AddReactionAsync(REMOVE_NICKNAME_EMOJI); await message.AddReactionAsync(KICK_EMOJI); await message.AddReactionAsync(WARN_EMOJI); await message.AddReactionAsync(BAN_EMOJI); await message.AddReactionAsync(NO_ACTION_EMOJI); - alertMessageIdsDic.Add(message.Id, badUser); - + _alertMessageIdsDic.Add(message.Id, badUser); } public async Task OnReactionAdded(Cacheable message, ISocketMessageChannel channel, SocketReaction reaction) { - var msg = message.Value as IUserMessage; + var msg = message.Value; var chan = channel as ITextChannel; if (reaction.User.Value.IsBot || msg == null || chan == null) @@ -81,113 +83,121 @@ public async Task OnReactionAdded(Cacheable message, ISocke return; } - if (alertMessageIdsDic != null && alertMessageIdsDic.ContainsKey(msg.Id)) + if (_alertMessageIdsDic != null && _alertMessageIdsDic.ContainsKey(msg.Id)) { - SocketGuildUser badUser; - alertMessageIdsDic.TryGetValue(msg.Id, out badUser); + _alertMessageIdsDic.TryGetValue(msg.Id, out var badUser); + var moderator = badUser.Guild.GetUser(reaction.UserId); - if (reaction.Emote.Name.Equals(BAN_EMOJI.Name)) + if (reaction.Emote.Name.Equals(WARN_EMOJI.Name)) { try { - //sends message to user - EmbedBuilder builder = new EmbedBuilder(); - builder.Title = "⚖️ Ban Notification"; - builder.Description = $"You have been banned from {badUser.Guild.Name}"; - builder.AddField("Reason", "Banned by BOT for an inappropriate name"); - builder.Color = Color.DarkOrange; - - await badUser.SendMessageAsync("", false, builder.Build()); - await badUser.Guild.AddBanAsync(badUser, 0, $"{moderator.Username}#{moderator.Discriminator} ({moderator.Id}) -> Inappropriate Name"); - await channel.SendMessageAsync($"Got it! I banned {badUser.Username}#{badUser.Discriminator}!"); + await using (var floofDb = new FloofDataContext()) + { + floofDb.Add(new Warning + { + DateAdded = DateTime.Now, + Forgiven = false, + GuildId = badUser.Guild.Id, + Moderator = $"{moderator.Username}#{moderator.Discriminator}", + ModeratorId = moderator.Id, + Reason = $"{moderator.Username}#{moderator.Discriminator} -> Warned by BOT for an inappropriate name", + UserId = badUser.Id + }); + + await floofDb.SaveChangesAsync(); + + var builder = new EmbedBuilder + { + Title = "⚖️ Warn Notification", + Description = $"You have recieved a warning in {badUser.Guild.Name}", + Color = Color.DarkOrange + }.AddField("Reason", "Warned by BOT for an inappropriate name"); + + await badUser.SendMessageAsync(string.Empty, false, builder.Build()); + await channel.SendMessageAsync($"Got it! I warned {badUser.Username}#{badUser.Discriminator}!"); + } } - catch (Exception ex) + catch (Exception e) { - await channel.SendMessageAsync("Unable to ban user. Do I have the permissions?"); - Log.Error("Unable to ban user for bad name: " + ex); + await channel.SendMessageAsync("Unable to warn user. Do I have the permissions?"); + Log.Error("Unable to warn user for bad name: " + e); } - alertMessageIdsDic.Remove(msg.Id); + + _alertMessageIdsDic.Remove(msg.Id); + return; } - else if (reaction.Emote.Name.Equals(WARN_EMOJI.Name)) + + if (reaction.Emote.Name.Equals(BAN_EMOJI.Name)) { try { - FloofDataContext _floofDb = new FloofDataContext(); - _floofDb.Add(new Warning + // Sends message to user + var builder = new EmbedBuilder { - DateAdded = DateTime.Now, - Forgiven = false, - GuildId = badUser.Guild.Id, - Moderator = $"{moderator.Username}#{moderator.Discriminator}", - ModeratorId = moderator.Id, - Reason = $"{moderator.Username}#{moderator.Discriminator} -> Warned by BOT for an inappropriate name", - UserId = badUser.Id - }); - _floofDb.SaveChanges(); - - EmbedBuilder builder = new EmbedBuilder(); - builder.Title = "⚖️ Warn Notification"; - builder.Description = $"You have recieved a warning in {badUser.Guild.Name}"; - builder.AddField("Reason", "Warned by BOT for an inappropriate name"); - builder.Color = Color.DarkOrange; - await badUser.SendMessageAsync("", false, builder.Build()); - - await channel.SendMessageAsync($"Got it! I warned {badUser.Username}#{badUser.Discriminator}!"); + Title = "⚖️ Ban Notification", + Description = $"You have been banned from {badUser.Guild.Name}", + Color = Color.DarkOrange + }.AddField("Reason", "Banned by BOT for an inappropriate name"); + + await badUser.SendMessageAsync(string.Empty, false, builder.Build()); + await badUser.Guild.AddBanAsync(badUser, 0, $"{moderator.Username}#{moderator.Discriminator} ({moderator.Id}) -> Inappropriate Name"); + await channel.SendMessageAsync($"Got it! I banned {badUser.Username}#{badUser.Discriminator}!"); } - catch (Exception ex) + catch (Exception e) { - await channel.SendMessageAsync("Unable to warn user. Do I have the permissions?"); - Log.Error("Unable to warn user for bad name: " + ex); + await channel.SendMessageAsync("Unable to ban user. Do I have the permissions?"); + Log.Error("Unable to ban user for bad name: " + e); } - alertMessageIdsDic.Remove(msg.Id); - return; + + _alertMessageIdsDic.Remove(msg.Id); } else if (reaction.Emote.Name.Equals(KICK_EMOJI.Name)) { try { - //sends message to user - EmbedBuilder builder = new EmbedBuilder(); - builder.Title = "🥾 Kick Notification"; - builder.Description = $"You have been Kicked from {badUser.Guild.Name}"; - builder.AddField("Reason", "Kicked by BOT for an inappropriate name"); - builder.Color = Color.DarkOrange; - await badUser.SendMessageAsync("", false, builder.Build()); - + // Sends message to user + var builder = new EmbedBuilder + { + Title = "🥾 Kick Notification", + Description = $"You have been Kicked from {badUser.Guild.Name}", + Color = Color.DarkOrange + }.AddField("Reason", "Kicked by BOT for an inappropriate name"); + + await badUser.SendMessageAsync(string.Empty, false, builder.Build()); await badUser.KickAsync($"{badUser.Username}#{badUser.Discriminator} -> Inappropriate Name"); await channel.SendMessageAsync($"Got it! I kicked {badUser.Username}#{badUser.Discriminator}!"); } - catch (Exception ex) + catch (Exception e) { await channel.SendMessageAsync("Unable to kick user. Do I have the permissions?"); - Log.Error("Unable to kick user for bad name: " + ex); + Log.Error("Unable to kick user for bad name: " + e); } - alertMessageIdsDic.Remove(msg.Id); + + _alertMessageIdsDic.Remove(msg.Id); } else if (reaction.Emote.Name.Equals(REMOVE_NICKNAME_EMOJI.Name)) { try { - await badUser.Guild.GetUser(badUser.Id).ModifyAsync(user => user.Nickname = ""); + await badUser.Guild.GetUser(badUser.Id).ModifyAsync(user => user.Nickname = string.Empty); await channel.SendMessageAsync($"Got it! I removed {badUser.Username}#{badUser.Discriminator}'s nickname!"); } - catch (Exception ex) + catch (Exception e) { await channel.SendMessageAsync("Unable to remove their nickname. Do I have the permissions?"); - Log.Error("Unable to remove nickname for bad name: " + ex); + Log.Error("Unable to remove nickname for bad name: " + e); } - alertMessageIdsDic.Remove(msg.Id); + _alertMessageIdsDic.Remove(msg.Id); } else if (reaction.Emote.Name.Equals(NO_ACTION_EMOJI.Name)) { await channel.SendMessageAsync($"Got it! I took no action against {badUser.Username}#{badUser.Discriminator}!"); - alertMessageIdsDic.Remove(msg.Id); + _alertMessageIdsDic.Remove(msg.Id); } - return; } } - } } diff --git a/Floofbot/Services/RaidProtectionService.cs b/Floofbot/Services/RaidProtectionService.cs index fcec2296..efbeaf51 100644 --- a/Floofbot/Services/RaidProtectionService.cs +++ b/Floofbot/Services/RaidProtectionService.cs @@ -14,20 +14,19 @@ namespace Floofbot.Services { public class RaidProtectionService { - // filter service + // Filter service WordFilterService _wordFilterService; - // load raid config - private Dictionary raidConfig; - // will store user id and how many counts they've had + // Load raid config + // Will store user id and how many counts they've had private Dictionary> userPunishmentCount = new Dictionary>(); - // used to keep track of the number of messages a user sent for spam protection + // Used to keep track of the number of messages a user sent for spam protection private Dictionary> userMessageCount = new Dictionary>(); - // used to track the last message a user has sent in the server + // Used to track the last message a user has sent in the server private Dictionary> lastUserMessageInGuild = new Dictionary>(); - // a list of punished users used to detect any potential raids + // A list of punished users used to detect any potential raids private Dictionary> punishedUsers = new Dictionary>(); - // contains the number of joins in a guild in a short time frame + // Contains the number of joins in a guild in a short time frame private Dictionary numberOfJoins = new Dictionary(); // these ints hold the raid protection config parameters @@ -49,8 +48,10 @@ public class RaidProtectionService public RaidProtectionService() { - raidConfig = BotConfigFactory.Config.RaidProtection; + var raidConfig = BotConfigFactory.Config.RaidProtection; + _wordFilterService = new WordFilterService(); + maxMentionCount = raidConfig["MaxMentionCount"]; forgivenDuration = raidConfig["ForgivenDuration"]; durationForMaxMessages = raidConfig["DurationForMaxMessages"]; @@ -67,259 +68,283 @@ public RaidProtectionService() durationBetweenMessages = raidConfig["DurationBetweenMessages"]; maxMessageSpam = raidConfig["MaxMessageSpam"]; } - public RaidProtectionConfig GetServerConfig(IGuild guild, FloofDataContext _floofDb) + + private RaidProtectionConfig GetServerConfig(IGuild guild, FloofDataContext _floofDb) { - RaidProtectionConfig serverConfig = _floofDb.RaidProtectionConfigs.Find(guild.Id); + var serverConfig = _floofDb.RaidProtectionConfigs.Find(guild.Id); + return serverConfig; } + private async Task NotifyModerators(SocketRole modRole, ITextChannel modChannel, string message) { if (modRole == null || modChannel == null || string.IsNullOrEmpty(message)) { Log.Information("Unable to notify moderators of a possible raid with reason: ``" + message + "``"); + return; } + await modChannel.SendMessageAsync(modRole.Mention + " there may be a possible raid! Reason: ``" + message + "``"); } + private async void SendMessageAndDelete(string messageContent, ISocketMessageChannel channel) { var botMsg = await channel.SendMessageAsync(messageContent); + await Task.Delay(botMessageDeletionDelay); await botMsg.DeleteAsync(); } + private async void UserPunishmentTimeout(ulong guildId, ulong userId) { await Task.Delay(forgivenDuration); - if (userPunishmentCount.ContainsKey(guildId) && userPunishmentCount[guildId].ContainsKey(userId)) - { - userPunishmentCount[guildId][userId] -= 1; - if (userPunishmentCount[guildId][userId] == 0) - userPunishmentCount[guildId].Remove(userId); - } + + if (!userPunishmentCount.ContainsKey(guildId) || !userPunishmentCount[guildId].ContainsKey(userId)) return; + + userPunishmentCount[guildId][userId] -= 1; + + if (userPunishmentCount[guildId][userId] == 0) + userPunishmentCount[guildId].Remove(userId); } - private async void punishedUsersTimeout(ulong guildId, SocketUser user) + + private async void PunishedUsersTimeout(ulong guildId, SocketUser user) { await Task.Delay(removePunishedUserDelay); + if (punishedUsers.ContainsKey(guildId) && punishedUsers[guildId].Contains(user)) punishedUsers[guildId].Remove(user); } - private async void userJoinTimeout(IGuild guild) + + private async void UserJoinTimeout(IGuild guild) { await Task.Delay(userJoinsDelay); - if (numberOfJoins.ContainsKey(guild) && numberOfJoins[guild] != 0) - { - numberOfJoins[guild] -= 1; - if (numberOfJoins[guild] == 0) - numberOfJoins.Remove(guild); - } - + + if (!numberOfJoins.ContainsKey(guild) || numberOfJoins[guild] == 0) return; + + numberOfJoins[guild] -= 1; + + if (numberOfJoins[guild] == 0) + numberOfJoins.Remove(guild); } + private async void UserMessageCountTimeout(ulong guildId, ulong userId) { await Task.Delay(durationForMaxMessages); - if (userMessageCount.ContainsKey(guildId) && userMessageCount[guildId].ContainsKey(userId)) - if (userMessageCount[guildId][userId] != 0) - { - userMessageCount[guildId][userId] -= 1; - if (userMessageCount[guildId][userId] == 0) - userMessageCount[guildId].Remove(userId); - - } + if (!userMessageCount.ContainsKey(guildId) || !userMessageCount[guildId].ContainsKey(userId)) return; + + if (userMessageCount[guildId][userId] == 0) return; + + userMessageCount[guildId][userId] -= 1; + + if (userMessageCount[guildId][userId] == 0) + userMessageCount[guildId].Remove(userId); } - private void ensureGuildInDictionaries(ulong guildId) + + private void EnsureGuildInDictionaries(ulong guildId) { if (!userPunishmentCount.ContainsKey(guildId)) userPunishmentCount.Add(guildId, new Dictionary()); + if (!userMessageCount.ContainsKey(guildId)) userMessageCount.Add(guildId, new Dictionary()); + if (!punishedUsers.ContainsKey(guildId)) punishedUsers.Add(guildId, new List()); + if (!lastUserMessageInGuild.ContainsKey(guildId)) lastUserMessageInGuild.Add(guildId, new Dictionary()); } - private bool CheckMessageForFilteredWords(SocketMessage msg, ulong guildId) - { - bool hasBadWord = _wordFilterService.hasFilteredWord(new FloofDataContext(), msg.Content, guildId, msg.Channel.Id); - - if (hasBadWord) + + private bool CheckMessageForFilteredWords(SocketMessage msg, ulong guildId) { - // add a bad boye point for the user + var hasBadWord = _wordFilterService.HasFilteredWord(new FloofDataContext(), msg.Content, guildId, msg.Channel.Id); + + if (!hasBadWord) return false; + + // Add a bad boye point for the user if (userPunishmentCount[guildId].ContainsKey(msg.Author.Id)) { userPunishmentCount[guildId][msg.Author.Id] += 1; } - else // they were a good boye but now they are not + else // They were a good boye but now they are not { userPunishmentCount[guildId].Add(msg.Author.Id, 1); } - // we run an async task to remove their point after the specified duration + + // We run an async task to remove their point after the specified duration UserPunishmentTimeout(guildId, msg.Author.Id); SendMessageAndDelete($"{msg.Author.Mention} There was a filtered word in that message. Please be mindful of your language!", msg.Channel); Log.Information("User ID " + msg.Author.Id + " triggered the word filter."); - // we return here because we only need to check for at least one match, doesnt matter if there are more + // We return here because we only need to check for at least one match, doesnt matter if there are more return true; } - else - { - return false; - } - - } - private bool CheckUserMessageCount(SocketMessage msg, ulong guildId) + + private bool CheckUserMessageCount(SocketMessage msg, ulong guildId) { if (userMessageCount[guildId].ContainsKey(msg.Author.Id)) { - // update last user message in server + // Update last user message in server if (lastUserMessageInGuild[guildId].ContainsKey(msg.Author.Id)) { lastUserMessageInGuild[guildId][msg.Author.Id] = msg; } - // record last user message in server + // Record last user message in server else { lastUserMessageInGuild[guildId].Add(msg.Author.Id, msg); return false; } - // compare timestamps of messages - TimeSpan timeBetweenMessages = msg.Timestamp - lastUserMessageInGuild[guildId][msg.Author.Id].Timestamp; + // Compare timestamps of messages + var timeBetweenMessages = msg.Timestamp - lastUserMessageInGuild[guildId][msg.Author.Id].Timestamp; + if (timeBetweenMessages.TotalSeconds < durationBetweenMessages) userMessageCount[guildId][msg.Author.Id] += 1; else return false; - if (userMessageCount[guildId][msg.Author.Id] >= maxMessageSpam) // no more than a defined number of messages in time frame + if (userMessageCount[guildId][msg.Author.Id] >= maxMessageSpam) // No more than a defined number of messages in time frame { - // add a bad boye point for the user + // Add a bad boye point for the user if (userPunishmentCount[guildId].ContainsKey(msg.Author.Id)) { userPunishmentCount[guildId][msg.Author.Id] += 1; } - else // they were a good boye but now they are not + else // They were a good boye but now they are not { userPunishmentCount[guildId].Add(msg.Author.Id, 1); } - // reset the count after punishing the user + + // Reset the count after punishing the user userMessageCount[guildId][msg.Author.Id] = 0; SendMessageAndDelete(msg.Author.Mention + " you are sending messages too quickly!", msg.Channel); - // we run an async task to remove their point after the specified duration + // We run an async task to remove their point after the specified duration UserPunishmentTimeout(guildId, msg.Author.Id); Log.Information("User ID " + msg.Author.Id + " triggered excess message spam and received a warning."); return true; } } - else // they were a good boye but now they are not + else // They were a good boye but now they are not { userMessageCount[guildId].Add(msg.Author.Id, 1); } - // we run an async task to remove their point after the specified duration + + // We run an async task to remove their point after the specified duration UserMessageCountTimeout(guildId, msg.Author.Id); return false; } + private async Task CheckMentions(SocketUserMessage msg, IGuild guild, SocketRole modRole, ITextChannel modChannel) { - if (msg.MentionedUsers.Count > maxMentionCount) + if (msg.MentionedUsers.Count <= maxMentionCount) return false; + + var reason = "Raid Protection =>" + msg.MentionedUsers.Count + " mentions in one message."; + + try { - string reason = "Raid Protection =>" + msg.MentionedUsers.Count + " mentions in one message."; - try + //sends message to user + var builder = new EmbedBuilder { - //sends message to user - EmbedBuilder builder = new EmbedBuilder(); - builder.Title = "⚖️ Ban Notification"; - builder.Description = $"You have been banned from {guild.Name}"; - builder.AddField("Reason", reason); - builder.Color = Discord.Color.Red; - await msg.Author.SendMessageAsync("", false, builder.Build()); - await guild.AddBanAsync(msg.Author, 0, reason); - - await NotifyModerators(modRole, modChannel, " I have banned a user for mentioning " + msg.MentionedUsers.Count() + " members"); - } - catch (Exception e) - { - Log.Error("Error banning user for mass mention: " + e); - } - Log.Information("User ID " + msg.Author.Id + " triggered excess mention spam and was banned."); - return true; + Title = "⚖️ Ban Notification", + Description = $"You have been banned from {guild.Name}", + Color = Color.Red + }.AddField("Reason", reason); + + await msg.Author.SendMessageAsync(string.Empty, false, builder.Build()); + + await guild.AddBanAsync(msg.Author, 0, reason); + + await NotifyModerators(modRole, modChannel, " I have banned a user for mentioning " + msg.MentionedUsers.Count() + " members"); } - return false; + catch (Exception e) + { + Log.Error("Error banning user for mass mention: " + e); + } + + Log.Information("User ID " + msg.Author.Id + " triggered excess mention spam and was banned."); + return true; } + private bool CheckLetterSpam(SocketMessage msg, ulong guildId) { - bool isMatch = Regex.IsMatch(msg.Content.ToLower(), @"((.)\2{" + maxNumberSequentialCharacters + @",})|((\w\S+)(?=((.{0," + (distanceBetweenPhrases - 1) + @"}|\s*)\4){" + (repeatingPhrasesLimit - 1) + @"}))"); - if (isMatch) + var isMatch = Regex.IsMatch(msg.Content.ToLower(), @"((.)\2{" + maxNumberSequentialCharacters + @",})|((\w\S+)(?=((.{0," + (distanceBetweenPhrases - 1) + @"}|\s*)\4){" + (repeatingPhrasesLimit - 1) + @"}))"); + if (!isMatch) return false; + + // Add a bad boye point for the user + if (userPunishmentCount[guildId].ContainsKey(msg.Author.Id)) { - // add a bad boye point for the user - if (userPunishmentCount[guildId].ContainsKey(msg.Author.Id)) - { - userPunishmentCount[guildId][msg.Author.Id] += 1; - } - else // they were a good boye but now they are not - { - userPunishmentCount[guildId].Add(msg.Author.Id, 1); - } - // we run an async task to remove their point after the specified duration - UserPunishmentTimeout(guildId, msg.Author.Id); - - SendMessageAndDelete(msg.Author.Mention + " no spamming!", msg.Channel); + userPunishmentCount[guildId][msg.Author.Id] += 1; + } + else // They were a good boye but now they are not + { + userPunishmentCount[guildId].Add(msg.Author.Id, 1); + } + + // We run an async task to remove their point after the specified duration + UserPunishmentTimeout(guildId, msg.Author.Id); - Log.Information("User ID " + msg.Author.Id + " triggered excess letter/phrase spam and received a warning."); + SendMessageAndDelete(msg.Author.Mention + " no spamming!", msg.Channel); - // we return here because we only need to check for at least one match, doesnt matter if there are more - return true; - } - return false; + Log.Information("User ID " + msg.Author.Id + " triggered excess letter/phrase spam and received a warning."); + // We return here because we only need to check for at least one match, doesnt matter if there are more + return true; } + private bool CheckInviteLinks(SocketMessage msg, ulong guildId) { var regex = "(https?:\\/\\/)?(www\\.)?((discord(app)?\\.com\\/invite)|(discord\\.(gg|io|me|li)))\\/+\\w{2,}\\/?"; - if (Regex.IsMatch(msg.Content, regex)) + + if (!Regex.IsMatch(msg.Content, regex)) return false; + + // Add a bad boye point for the user + if (userPunishmentCount[guildId].ContainsKey(msg.Author.Id)) { - // add a bad boye point for the user - if (userPunishmentCount[guildId].ContainsKey(msg.Author.Id)) - { - userPunishmentCount[guildId][msg.Author.Id] += 1; - } - else // they were a good boye but now they are not - { - userPunishmentCount[guildId].Add(msg.Author.Id, 1); - } - // we run an async task to remove their point after the specified duration - UserPunishmentTimeout(guildId, msg.Author.Id); + userPunishmentCount[guildId][msg.Author.Id] += 1; + } + else // They were a good boye but now they are not + { + userPunishmentCount[guildId].Add(msg.Author.Id, 1); + } + + // We run an async task to remove their point after the specified duration + UserPunishmentTimeout(guildId, msg.Author.Id); - SendMessageAndDelete(msg.Author.Mention + " no invite links!", msg.Channel); + SendMessageAndDelete(msg.Author.Mention + " no invite links!", msg.Channel); - Log.Information("User ID " + msg.Author.Id + " triggered invite link spam and received a warning."); + Log.Information("User ID " + msg.Author.Id + " triggered invite link spam and received a warning."); - // we return here because we only need to check for at least one match, doesnt matter if there are more - return true; - } - return false; + // We return here because we only need to check for at least one match, doesnt matter if there are more + return true; } + private bool CheckEmojiSpam(SocketMessage msg, ulong guildId) { - // check for repeated custom and normal emojis. - // custom emojis have format <:name:id> and normal emojis use unicode emoji + // Check for repeated custom and normal emojis. + // Custom emojis have format <:name:id> and normal emojis use unicode emoji var regex = "( ?()|(\u00a9|\u00ae|[\u2000-\u200c]|[\u200e-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff]) ?){" + maxNumberEmojis + ",}"; var matchEmoji = Regex.Match(msg.Content, regex); - if (matchEmoji.Success) // emoji spam + + if (matchEmoji.Success) // Emoji spam { - // add a bad boye point for the user + // Add a bad boye point for the user if (userPunishmentCount[guildId].ContainsKey(msg.Author.Id)) { userPunishmentCount[guildId][msg.Author.Id] += 1; } - else // they were a good boye but now they are not + else // They were a good boy, but now they are not anymore { userPunishmentCount[guildId].Add(msg.Author.Id, 1); } - // we run an async task to remove their point after the specified duration + + // We run an async task to remove their point after the specified duration UserPunishmentTimeout(guildId, msg.Author.Id); Log.Information("User ID " + msg.Author.Id + " triggered emoji spam and received a warning."); @@ -328,68 +353,86 @@ private bool CheckEmojiSpam(SocketMessage msg, ulong guildId) return true; } - else - return false; + + return false; } + public async Task CheckForExcessiveJoins(IGuild guild) { var server = guild as SocketGuild; + if (server == null) return; - var _floofDb = new FloofDataContext(); - var serverConfig = GetServerConfig(guild, _floofDb); - // raid protection not configured or disabled - if (serverConfig == null || !serverConfig.Enabled) - return; - // increment user join count for guild - if (numberOfJoins.ContainsKey(guild)) + + await using (var floofDb = new FloofDataContext()) { - numberOfJoins[guild] += 1; - if (numberOfJoins[guild] >= maxNumberOfJoins) - { - var modRole = (serverConfig.ModRoleId != null) ? server.GetRole((ulong)serverConfig.ModRoleId) : null; - var modChannel = (serverConfig.ModChannelId != null) ? server.GetChannel((ulong)serverConfig.ModChannelId) as ITextChannel : null; - await NotifyModerators(modRole, modChannel, "Excessive number of new joins in short time period."); - Log.Information("An excessive number of joins was detected in server ID " + server.Id); - numberOfJoins[guild] = 0; + var serverConfig = GetServerConfig(guild, floofDb); + + // Raid protection not configured or disabled + if (serverConfig == null || !serverConfig.Enabled) return; + + // Increment user join count for guild + if (numberOfJoins.ContainsKey(guild)) + { + numberOfJoins[guild] += 1; + + if (numberOfJoins[guild] >= maxNumberOfJoins) + { + var modRole = (serverConfig.ModRoleId != null) ? server.GetRole((ulong)serverConfig.ModRoleId) : null; + var modChannel = (serverConfig.ModChannelId != null) ? server.GetChannel((ulong)serverConfig.ModChannelId) as ITextChannel : null; + + await NotifyModerators(modRole, modChannel, "Excessive number of new joins in short time period."); + + Log.Information("An excessive number of joins was detected in server ID " + server.Id); + + numberOfJoins[guild] = 0; + return; + } } + else // add 1 to number of joins in that guild + { + numberOfJoins.Add(guild, 1); + } + + UserJoinTimeout(guild); } - else // add 1 to number of joins in that guild - { - numberOfJoins.Add(guild, 1); - } - userJoinTimeout(guild); - } + // return true is message triggered raid protection, false otherwise public async Task CheckMessage(FloofDataContext _floofDb, SocketMessage msg) { - - // can return null + // Can return null var userMsg = msg as SocketUserMessage; + if (userMsg == null || msg.Author.IsBot) return false; + // can return null var channel = userMsg.Channel as ITextChannel; + if (channel == null) return false; + var guild = channel.Guild as SocketGuild; + if (guild == null) return false; var serverConfig = GetServerConfig(guild, _floofDb); - // raid protection not configured or disabled + + // Raid protection not configured or disabled if (serverConfig == null || !serverConfig.Enabled) return false; - // users with the exceptions role are immune to raid protection + // Users with the exceptions role are immune to raid protection if (serverConfig.ExceptionRoleId != null) { - // returns null if exception role doe not exist anymore + // Returns null if exception role doe not exist anymore var exceptionsRole = guild.GetRole((ulong)serverConfig.ExceptionRoleId); var guildUser = guild.GetUser(userMsg.Author.Id); - // role must exist and user must exist in server + + // Role must exist and user must exist in server if (exceptionsRole != null && guildUser != null) { foreach (IRole role in guildUser.Roles) @@ -400,102 +443,106 @@ public async Task CheckMessage(FloofDataContext _floofDb, SocketMessage ms } } - // get other values from db and get their associated roles and channels + // Get other values from db and get their associated roles and channels var mutedRole = (serverConfig.MutedRoleId != null) ? guild.GetRole((ulong)serverConfig.MutedRoleId) : null; var modRole = (serverConfig.ModRoleId != null) ? guild.GetRole((ulong)serverConfig.ModRoleId) : null; var modChannel = (serverConfig.ModChannelId != null) ? guild.GetChannel((ulong)serverConfig.ModChannelId) as ITextChannel : null; var banOffenders = serverConfig.BanOffenders; - // ensure our dictionaries contain the guild - ensureGuildInDictionaries(guild.Id); - // now we run our checks. If any of them return true, we have a bad boy - - // checks for filtered words - bool filteredWord = CheckMessageForFilteredWords(userMsg, guild.Id); - // this will ALWAYS ban users regardless of muted role or not - bool spammedMentions = CheckMentions(userMsg, guild, modRole, modChannel).Result; - // this will check their messages and see if they are spamming - bool userSpammedMessages = CheckUserMessageCount(userMsg, guild.Id); - // this checks for spamming letters in a row - bool userSpammedLetters = CheckLetterSpam(userMsg, guild.Id); - // this checks for posting invite links - bool userSpammedInviteLink = CheckInviteLinks(userMsg, guild.Id); - // check for spammed emojis - bool userSpammedEmojis = CheckEmojiSpam(userMsg, guild.Id); + // Ensure our dictionaries contain the guild + EnsureGuildInDictionaries(guild.Id); + // Now we run our checks. If any of them return true, we have a bad boy + + // Checks for filtered words + var filteredWord = CheckMessageForFilteredWords(userMsg, guild.Id); + // This will ALWAYS ban users regardless of muted role or not + var spammedMentions = CheckMentions(userMsg, guild, modRole, modChannel).Result; + // This will check their messages and see if they are spamming + var userSpammedMessages = CheckUserMessageCount(userMsg, guild.Id); + // This checks for spamming letters in a row + var userSpammedLetters = CheckLetterSpam(userMsg, guild.Id); + // This checks for posting invite links + var userSpammedInviteLink = CheckInviteLinks(userMsg, guild.Id); + // Check for spammed emojis + var userSpammedEmojis = CheckEmojiSpam(userMsg, guild.Id); if (spammedMentions) - return false; // user already banned - if (filteredWord || userSpammedMessages || userSpammedLetters || userSpammedInviteLink || userSpammedEmojis) + return false; // User already banned + + if (!filteredWord && !userSpammedMessages && !userSpammedLetters && !userSpammedInviteLink && !userSpammedEmojis) return false; + + if (!userPunishmentCount[guild.Id].ContainsKey(userMsg.Author.Id)) return true; + + // They have been too much of a bad boy >:( + if (userPunishmentCount[guild.Id][msg.Author.Id] <= maxNumberOfPunishments) return true; + + // Remove them from the dictionary + userPunishmentCount[guild.Id].Remove(userMsg.Author.Id); + + // Add to the list of punished users + punishedUsers[guild.Id].Add(userMsg.Author); + PunishedUsersTimeout(guild.Id, userMsg.Author); + // Decide if we need to notify the mods of a potential raid + + if ((modRole != null) && (modChannel != null) && (punishedUsers.Count >= maxNumberPunishedUsers)) + await NotifyModerators(modRole, modChannel, + "Excessive amount of users punished in short time frame."); + + // If the muted role is set and we are not banning people + if ((mutedRole != null) && (!banOffenders)) { - if (userPunishmentCount[guild.Id].ContainsKey(userMsg.Author.Id)) + var guildUser = guild.GetUser(userMsg.Author.Id); + + try { - // they have been too much of a bad boye >:( - if (userPunishmentCount[guild.Id][msg.Author.Id] > maxNumberOfPunishments) + await guildUser.AddRoleAsync(mutedRole); + await msg.Channel.SendMessageAsync(userMsg.Author.Mention + + " you have received too many warnings. You are muted as a result."); + + if (modChannel != null) { - // remove them from the dictionary - userPunishmentCount[guild.Id].Remove(userMsg.Author.Id); - // add to the list of punished users - punishedUsers[guild.Id].Add(userMsg.Author); - punishedUsersTimeout(guild.Id, userMsg.Author); - // decide if we need to notify the mods of a potential raid - if ((modRole != null) && (modChannel != null) && (punishedUsers.Count >= maxNumberPunishedUsers)) - await NotifyModerators(modRole, modChannel, "Excessive amount of users punished in short time frame."); - // if the muted role is set and we are not banning people - if ((mutedRole != null) && (!banOffenders)) - { - var guildUser = guild.GetUser(userMsg.Author.Id); - try - { - await guildUser.AddRoleAsync(mutedRole); - await msg.Channel.SendMessageAsync(userMsg.Author.Mention + " you have received too many warnings. You are muted as a result."); - - if (modChannel != null) - { - var embed = new EmbedBuilder(); - - embed.WithTitle($"🔇 User Muted | {userMsg.Author.Username}#{userMsg.Author.Discriminator}") - .WithColor(Color.Teal) - .WithDescription($"{userMsg.Author.Mention} | ``{userMsg.Author.Id}`` has been automatically muted by the raid protection system.") - .WithCurrentTimestamp(); - - if (Uri.IsWellFormedUriString(userMsg.Author.GetAvatarUrl(), UriKind.Absolute)) - embed.WithThumbnailUrl(userMsg.Author.GetAvatarUrl()); - - await modChannel.SendMessageAsync("", false, embed.Build()); - } - } - catch (Exception e) - { - Log.Error("Unable to mute user for raid protection: " + e); - } - return true; - } - else // ban user by default - { - try - { - string reason = "Raid Protection => Triggered too many bot responses"; - //sends message to user - EmbedBuilder builder = new EmbedBuilder(); - builder.Title = "⚖️ Ban Notification"; - builder.Description = $"You have been banned from {guild.Name}"; - builder.AddField("Reason", reason); - builder.Color = Discord.Color.Red; - await msg.Author.SendMessageAsync("", false, builder.Build()); - await guild.AddBanAsync(msg.Author, 0, reason); - } - catch (Exception e) - { - Log.Error("Unable to ban user for raid protection: " + e); - } - return false; // dont need to delete message as the ban already handled that - } + var embed = new EmbedBuilder(); + + embed.WithTitle($"🔇 User Muted | {userMsg.Author.Username}#{userMsg.Author.Discriminator}") + .WithColor(Color.Teal) + .WithDescription( + $"{userMsg.Author.Mention} | ``{userMsg.Author.Id}`` has been automatically muted by the raid protection system.") + .WithCurrentTimestamp(); + if (Uri.IsWellFormedUriString(userMsg.Author.GetAvatarUrl(), UriKind.Absolute)) + embed.WithThumbnailUrl(userMsg.Author.GetAvatarUrl()); + + await modChannel.SendMessageAsync(string.Empty, false, embed.Build()); } } + catch (Exception e) + { + Log.Error("Unable to mute user for raid protection: " + e); + } + return true; } - return false; + + try + { + var reason = "Raid Protection => Triggered too many bot responses"; + //sends message to user + var builder = new EmbedBuilder + { + Title = "⚖️ Ban Notification", + Description = $"You have been banned from {guild.Name}", + Color = Color.Red + }.AddField("Reason", reason); + + await msg.Author.SendMessageAsync(string.Empty, false, builder.Build()); + await guild.AddBanAsync(msg.Author, 0, reason); + } + catch (Exception e) + { + Log.Error("Unable to ban user for raid protection: " + e); + } + + return false; // dont need to delete message as the ban already handled that } } } diff --git a/Floofbot/Services/Repository/FloofDataContext.cs b/Floofbot/Services/Repository/FloofDataContext.cs index 61f7d531..4d163b0d 100644 --- a/Floofbot/Services/Repository/FloofDataContext.cs +++ b/Floofbot/Services/Repository/FloofDataContext.cs @@ -4,7 +4,7 @@ namespace Floofbot.Services.Repository { - public partial class FloofDataContext : DbContext + public class FloofDataContext : DbContext { public FloofDataContext() { @@ -32,12 +32,12 @@ public FloofDataContext(DbContextOptions options) public virtual DbSet RaidProtectionConfigs { get; set; } public virtual DbSet UserRolesLists { get; set; } public virtual DbSet WelcomeGateConfigs { get; set; } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - if (!optionsBuilder.IsConfigured) - { - optionsBuilder.UseSqlite("DataSource=" + BotConfigFactory.Config.DbPath); - } + if (optionsBuilder.IsConfigured) return; + + optionsBuilder.UseSqlite("DataSource=" + BotConfigFactory.Config.DbPath); } } -} +} \ No newline at end of file diff --git a/Floofbot/Services/UserRoleRetentionService.cs b/Floofbot/Services/UserRoleRetentionService.cs index 87304edd..359fbb53 100644 --- a/Floofbot/Services/UserRoleRetentionService.cs +++ b/Floofbot/Services/UserRoleRetentionService.cs @@ -10,19 +10,23 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; namespace Floofbot.Services { class UserRoleRetentionService { FloofDataContext _floofDb; + public UserRoleRetentionService(FloofDataContext floofDb) { _floofDb = floofDb; } + public async Task LogUserRoles(IGuildUser user) { - string userRoles = string.Join(",", user.RoleIds); + var userRoles = string.Join(",", user.RoleIds); + await RemoveUserRoleLog(user); _floofDb.UserRolesLists.Add(new UserRolesList{ @@ -31,63 +35,64 @@ public async Task LogUserRoles(IGuildUser user) UserID = user.Id, UTCTimestamp = DateTime.Now }); + await _floofDb.SaveChangesAsync(); } - public async Task RemoveUserRoleLog(IGuildUser user) + + private async Task RemoveUserRoleLog(IGuildUser user) { // clear old roles in db with new ones if they exist - if (_floofDb.UserRolesLists.AsQueryable().Where(x => x.ServerId == user.Guild.Id && x.UserID == user.Id).Any()) + if (_floofDb.UserRolesLists.AsQueryable().Any(x => x.ServerId == user.Guild.Id && x.UserID == user.Id)) { - var oldUserRoles = _floofDb.UserRolesLists.AsQueryable().Where(x => x.ServerId == user.Guild.Id && x.UserID == user.Id).FirstOrDefault(); + var oldUserRoles = await _floofDb.UserRolesLists.AsQueryable().FirstOrDefaultAsync(x => x.ServerId == user.Guild.Id && x.UserID == user.Id); + _floofDb.UserRolesLists.Remove(oldUserRoles); + await _floofDb.SaveChangesAsync(); } } - public string GetUserRoles(IGuildUser user) + + private string GetUserRoles(IGuildUser user) { - if (_floofDb.UserRolesLists.AsQueryable().Where(x => x.ServerId == user.Guild.Id && x.UserID == user.Id).Any()) + if (_floofDb.UserRolesLists.AsQueryable().Any(x => x.ServerId == user.Guild.Id && x.UserID == user.Id)) { - var userRoles = _floofDb.UserRolesLists.AsQueryable().Where(x => x.ServerId == user.Guild.Id && x.UserID == user.Id).FirstOrDefault(); + var userRoles = _floofDb.UserRolesLists.AsQueryable().FirstOrDefault(x => x.ServerId == user.Guild.Id && x.UserID == user.Id); + return userRoles.ListOfRoleIds; } - else - { - return null; - } + + return null; } + public async Task RestoreUserRoles(IGuildUser user) { var oldUserRoles = GetUserRoles(user); - if (oldUserRoles != null) // user actually had old roles + if (oldUserRoles != null) // User actually had old roles { var oldUserRolesList = oldUserRoles.Split(","); - // restore each role 1 by 1 - foreach (string roleId in oldUserRolesList) + // Restore each role 1 by 1 + foreach (var roleId in oldUserRolesList) { - IRole role = user.Guild.GetRole(Convert.ToUInt64(roleId)); + var role = user.Guild.GetRole(Convert.ToUInt64(roleId)); if (role == null) // role does not exist { Log.Error("Unable to return role ID " + roleId + " to user ID " + user.Id + " as it does not exist anymore!"); continue; } - else if (role.Name == "@everyone") - { + + if (role.Name == "@everyone") continue; + + try + { + await user.AddRoleAsync(role); } - else + catch (Exception e) { - try - { - await user.AddRoleAsync(role); - } - catch (Exception ex) - { - Log.Information("Cannot return the role ID " + role.Id + " to user ID " + user.Id + ". Error: " + ex.ToString()); - continue; // try add next role - } + Log.Information("Cannot return the role ID " + role.Id + " to user ID " + user.Id + ". Error: " + e); } } } diff --git a/Floofbot/Services/WelcomeGateService.cs b/Floofbot/Services/WelcomeGateService.cs index b5049789..56de55ac 100644 --- a/Floofbot/Services/WelcomeGateService.cs +++ b/Floofbot/Services/WelcomeGateService.cs @@ -9,36 +9,37 @@ namespace Floofbot.Services { class WelcomeGateService { - private FloofDataContext _floofDb; - public WelcomeGateService(FloofDataContext floofDb) - { - _floofDb = floofDb; - } public async Task HandleWelcomeGate(SocketGuildUser before, SocketGuildUser after) { if (before.IsPending == after.IsPending) // no welcome gate change return; - FloofDataContext floofDb = new FloofDataContext(); - var guild = after.Guild; - WelcomeGate serverConfig = floofDb.WelcomeGateConfigs.Find(guild.Id); - - if (serverConfig == null || serverConfig.Toggle == false || serverConfig.RoleId == null) // disabled - return; - try + // We don't need to use a global variable for our context. + // We use a using statement for that to empty resources when we are done with what we want to do + await using (var floofDb = new FloofDataContext()) { - var userRole = guild.GetRole((ulong)serverConfig.RoleId); - if (userRole == null)// role does not exist anymore + var guild = after.Guild; + var serverConfig = await floofDb.WelcomeGateConfigs.FindAsync(guild.Id); + + if (serverConfig == null || serverConfig.Toggle == false || serverConfig.RoleId == null) // disabled + return; + + try { - Log.Error("Unable to automatically assign a role for the welcome gate - role does not exist"); - return; - } + var userRole = guild.GetRole((ulong) serverConfig.RoleId); + + if (userRole == null)// role does not exist anymore + { + Log.Error("Unable to automatically assign a role for the welcome gate - role does not exist"); + return; + } - await after.AddRoleAsync(userRole); - } - catch (Exception ex) - { - Log.Error("An exception occured when trying to add roles for the welcome gate: " + ex.ToString()); + await after.AddRoleAsync(userRole); + } + catch (Exception e) + { + Log.Error("An exception occured when trying to add roles for the welcome gate: " + e); + } } } } diff --git a/Floofbot/Services/WordFilterService.cs b/Floofbot/Services/WordFilterService.cs index 94406605..0c23df70 100644 --- a/Floofbot/Services/WordFilterService.cs +++ b/Floofbot/Services/WordFilterService.cs @@ -13,7 +13,7 @@ class WordFilterService List _filteredWords; DateTime _lastRefreshedTime; - public List filteredWordsInName(FloofDataContext floofDb, string messageContent, ulong serverId) // names + public List FilteredWordsInName(FloofDataContext floofDb, string messageContent, ulong serverId) // names { // return false if none of the serverIds match or filtering has been disabled for the server if (!floofDb.FilterConfigs.AsQueryable() @@ -22,7 +22,8 @@ public List filteredWordsInName(FloofDataContext floofDb, string message return null; } - DateTime currentTime = DateTime.Now; + var currentTime = DateTime.Now; + if (_lastRefreshedTime == null || currentTime.Subtract(_lastRefreshedTime).TotalMinutes >= 30) { _filteredWords = floofDb.FilteredWords.AsQueryable() @@ -30,21 +31,20 @@ public List filteredWordsInName(FloofDataContext floofDb, string message _lastRefreshedTime = currentTime; } - List DetectedWords = new List(); + var detectedWords = new List(); foreach (var filteredWord in _filteredWords) { if (messageContent.ToLower().Contains(filteredWord.Word.ToLower())) { - DetectedWords.Add(filteredWord.Word); + detectedWords.Add(filteredWord.Word); } } - if (DetectedWords.Count() == 0) - return null; - else - return DetectedWords; + + return !detectedWords.Any() ? null : detectedWords; } - public bool hasFilteredWord(FloofDataContext floofDb, string messageContent, ulong serverId, ulong channelId) // messages + + public bool HasFilteredWord(FloofDataContext floofDb, string messageContent, ulong serverId, ulong channelId) // messages { // return false if none of the serverIds match or filtering has been disabled for the server if (!floofDb.FilterConfigs.AsQueryable() @@ -60,17 +60,19 @@ public bool hasFilteredWord(FloofDataContext floofDb, string messageContent, ulo return false; } - DateTime currentTime = DateTime.Now; + var currentTime = DateTime.Now; + if (_lastRefreshedTime == null || currentTime.Subtract(_lastRefreshedTime).TotalMinutes >= 30) { _filteredWords = floofDb.FilteredWords.AsQueryable() .Where(x => x.ServerId == serverId).ToList(); + _lastRefreshedTime = currentTime; } foreach (var filteredWord in _filteredWords) { - Regex r = new Regex(@$"\b({Regex.Escape(filteredWord.Word)})\b", + var r = new Regex(@$"\b({Regex.Escape(filteredWord.Word)})\b", RegexOptions.IgnoreCase | RegexOptions.Singleline); if (r.IsMatch(messageContent)) { @@ -80,5 +82,4 @@ public bool hasFilteredWord(FloofDataContext floofDb, string messageContent, ulo return false; } } - } diff --git a/README.md b/README.md index 0f7ee917..88826ba2 100644 --- a/README.md +++ b/README.md @@ -8,5 +8,7 @@ Floofbot is yet another Discord bot. - .NET Core 3.1 ## Commands - - To build: Run `dotnet build` from the Floofbot directory, where `Floofbot.csproj` is located. + - To build: + + Run `dotnet restore` to restore all project dependencies + + Run `dotnet build` from the Floofbot directory, where `Floofbot.csproj` is located. - To run: Navigate to the build in the `bin` directory and run `Floofbot.exe` from there.