diff --git a/.gitignore b/.gitignore index efab521..0d48439 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files -BotCatMaxy/HiddenInfo.cs *.rsuser *.suo *.user @@ -338,4 +337,7 @@ ASALocalRun/ .localhistory/ # BeatPulse healthcheck temp database -healthchecksdb \ No newline at end of file +healthchecksdb + +# Tokens file +/BotCatMaxy/Properties/Tokens.targets \ No newline at end of file diff --git a/BotCatMaxy/BotCatMaxy.csproj b/BotCatMaxy/BotCatMaxy.csproj index c4afe93..0eab279 100644 --- a/BotCatMaxy/BotCatMaxy.csproj +++ b/BotCatMaxy/BotCatMaxy.csproj @@ -1,17 +1,16 @@ - + build$([System.DateTime]::UtcNow.ToString("yyyyMMddHHmmss")) Exe - netcoreapp3.1 + net5.0 BotCatMaxy.MainClass BotCatMaxy Blackcatmaxy + true - - C:\Users\bobth\Documents\Botcatmaxy true - false + false true @@ -23,19 +22,24 @@ - - - + + + + - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/BotCatMaxy/Components/Data/DataManipulator.cs b/BotCatMaxy/Components/Data/DataManipulator.cs index b326e15..7b8735a 100644 --- a/BotCatMaxy/Components/Data/DataManipulator.cs +++ b/BotCatMaxy/Components/Data/DataManipulator.cs @@ -5,6 +5,7 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Conventions; using MongoDB.Driver; using System; using System.Collections.Generic; @@ -23,6 +24,9 @@ public static async Task MapTypes() { try { + var conventionPack = new ConventionPack { new CamelCaseElementNameConvention() }; + ConventionRegistry.Register("camelCase", conventionPack, t => true); + BsonClassMap.RegisterClassMap(); BsonClassMap.RegisterClassMap(); BsonClassMap.RegisterClassMap(); @@ -120,12 +124,13 @@ public static T LoadFromFile(this IGuild guild, bool createFile = false) wher public static void SaveToFile(this T file) where T : DataObject { - if (file.guild == null) throw new InvalidOperationException("Data file does not have a guild"); - file.AddToCache(); var collection = file.guild.GetCollection(true); - collection.FindOneAndDelete(Builders.Filter.Eq("_id", typeof(T).Name)); - collection.InsertOne(file.ToBsonDocument()); + var name = typeof(T).Name; + collection.FindOneAndDelete(Builders.Filter.Eq("_id", name)); + var doc = file.ToBsonDocument(); + doc.Set("_id", name); + collection.InsertOne(doc); } public static IMongoCollection GetInfractionsCollection(this IGuild guild, bool createDir = true) @@ -211,6 +216,7 @@ public static void SaveInfractions(this ulong userID, IGuild guild, List 0) { message = guild.Roles.Where( role => (role.Permissions.Administrator && !role.IsManaged) || settings.allowedToLink.Contains(role.Id)).Select(role => role.Name).ToArray().ListItems("\n"); - if (message.NotEmpty()) embed.AddField("Roles that can post links", message, true); + if (message?.Length is not null or 0) embed.AddField("Roles that can post links", message, true); } } if (settings.allowedCaps > 0) @@ -113,7 +113,7 @@ public async Task ListAutoMod(string extension = "") if (settings.moderateNames) embed.AddField("Name moderation", "True", true); if (settings.maxNewLines != null) embed.AddField("Maximum new lines", $"{settings.maxNewLines.Value} new lines", true); } - if (badWords?.all != null && badWords.all.Count > 0 && (useExplicit || badWords.all.Any(word => !string.IsNullOrWhiteSpace(word.euphemism)))) + if (badWords?.all != null && badWords.all.Count > 0 && (useExplicit || badWords.all.Any(word => !string.IsNullOrWhiteSpace(word.Euphemism)))) { List words = new List(); foreach (List group in badWords.grouped) @@ -124,21 +124,21 @@ public async Task ListAutoMod(string extension = "") string word = ""; if (useExplicit) { - if (group.Count == 1 || group.All(badWord => badWord.size == first.size)) + if (group.Count == 1 || group.All(badWord => badWord.Size == first.Size)) { - word = $"[{first.size}x] "; + word = $"[{first.Size}x] "; } else { - var sizes = group.Select(badword => badword.size); + var sizes = group.Select(badword => badword.Size); word = $"[{sizes.Min()}-{sizes.Max()}x] "; } - if (first.euphemism.NotEmpty()) word += $"{first.euphemism} "; - word += $"({group.Select(badWord => $"{badWord.word}{(badWord.partOfWord ? "¤" : "")}").ToArray().ListItems(", ")})"; + if (first.Euphemism?.Length is not null or 0) word += $"{first.Euphemism} "; + word += $"({group.Select(badWord => $"{badWord.Word}{(badWord.PartOfWord ? "¤" : "")}").ToArray().ListItems(", ")})"; } - else if (!first.euphemism.IsNullOrEmpty()) - word = first.euphemism; - if (first.partOfWord && (!first.euphemism.IsNullOrEmpty() && !useExplicit)) + else if (!first.Euphemism.IsNullOrEmpty()) + word = first.Euphemism; + if (first.PartOfWord && (!first.Euphemism.IsNullOrEmpty() && !useExplicit)) { word += "¤"; } @@ -170,7 +170,7 @@ public async Task AllowEmojis(uint amount) settings.maxEmojis = amount; settings.SaveToFile(); string extraInfo = ""; - if (settings.allowedToLink.NotEmpty()) extraInfo = " except by role allowed to link"; + if (settings.allowedToLink?.Count is not null or 0) extraInfo = " except by role allowed to link"; if (amount == 0) await ReplyAsync("No emojis are allowed" + extraInfo); else await ReplyAsync($"Max {amount} emojis are allowed{extraInfo}"); } @@ -201,7 +201,7 @@ public async Task SetMaxEmojis(string amount) settings.maxEmojis = 0; settings.SaveToFile(); string extraInfo = ""; - if (settings.allowedToLink.NotEmpty()) extraInfo = " except by role allowed to link"; + if (settings.allowedToLink?.Count is not null or 0) extraInfo = " except by role allowed to link"; await ReplyAsync("Emojis are now no longer allowed" + extraInfo); break; default: @@ -322,23 +322,31 @@ public async Task RemoveAllowedLinkRole(SocketRole role) [HasAdmin()] public async Task ToggleContainBadWord(string word) { - BadWords badWords = new BadWords(Context.Guild); - foreach (BadWord badWord in badWords.all) + var file = Context.Guild.LoadFromFile(false); + var badWords = file?.badWords; + if (badWords?.Count is null or 0) { - if (badWord.word.ToLower() == word.ToLower()) + await ReplyAsync("No badwords have been set"); + return; + } + var editedList = new List(badWords); + for (int i = 0; i < badWords.Count; i++) + { + var badWord = badWords[i]; + if (badWord.Word.Equals(word, StringComparison.InvariantCultureIgnoreCase)) { - if (badWord.partOfWord) + if (badWord.PartOfWord) { - badWord.partOfWord = false; + editedList[i] = badWord with { PartOfWord = false }; await ReplyAsync("Set badword to not be filtered if it's inside of another word"); } else { - badWord.partOfWord = true; + editedList[i] = badWord with { PartOfWord = true }; await ReplyAsync("Set badword to be filtered even if it's inside of another word"); } - BadWordList badWordList = new BadWordList { badWords = badWords.all, guild = Context.Guild }; - badWordList.SaveToFile(); + file.badWords = editedList; + file.SaveToFile(); return; } } @@ -482,7 +490,7 @@ public async Task RemoveBadWord(string word) await ReplyAsync("No bad words are set"); return; } - BadWord badToRemove = badWordsClass.badWords.FirstOrDefault(badWord => badWord.word == word); + BadWord badToRemove = badWordsClass.badWords.FirstOrDefault(badWord => badWord.Word == word); if (badToRemove != null) { badWordsClass.badWords.Remove(badToRemove); @@ -504,15 +512,15 @@ public async Task AddBadWord(string word, string euphemism = null, float size = { BadWord badWord = new BadWord { - word = word, - euphemism = euphemism, - size = size + Word = word, + Euphemism = euphemism, + Size = size }; BadWordList badWordsClass = Context.Guild.LoadFromFile(true); badWordsClass.badWords.Add(badWord); badWordsClass.SaveToFile(); - await ReplyAsync($"Added {badWord.word}{((badWord.euphemism != null) ? $", also known as {badWord.euphemism}" : "")} to bad word list"); + await ReplyAsync($"Added {badWord.Word}{((badWord.Euphemism != null) ? $", also known as {badWord.Euphemism}" : "")} to bad word list"); } [Command("addanouncementchannel"), HasAdmin] @@ -554,8 +562,6 @@ public async Task ToggleNameFilter() ModerationSettings settings = Context.Guild.LoadFromFile(true); settings.moderateNames = !settings.moderateNames; settings.SaveToFile(); - string extra = ""; - if (settings.moderateNames) extra = " note: existing usernames and nicknames won't be filtered"; await ReplyAsync("Set user name and nickname filtering to " + settings.moderateNames.ToString().ToLowerInvariant()); } @@ -619,4 +625,4 @@ public async Task RemoveInviteWhitelist(ulong guildID) } } } -} \ No newline at end of file +} diff --git a/BotCatMaxy/Components/Filter/FilterHandler.cs b/BotCatMaxy/Components/Filter/FilterHandler.cs index 6d5d7b6..a48c08c 100644 --- a/BotCatMaxy/Components/Filter/FilterHandler.cs +++ b/BotCatMaxy/Components/Filter/FilterHandler.cs @@ -85,15 +85,15 @@ public async Task CheckNameInGuild(IUser user, string name, SocketGuild guild) embed.WithColor(Color.DarkMagenta); embed.WithAuthor(user); embed.WithTitle("User kicked for bad username"); - embed.WithDescription($"Name '{name}' contained '{detectedBadWord.word}'"); + embed.WithDescription($"Name '{name}' contained '{detectedBadWord.Word}'"); embed.WithCurrentTimestamp(); embed.WithFooter("User ID: " + user.Id); await (channel as SocketTextChannel).SendMessageAsync(embed: embed.Build()); } //If user's DMs aren't blocked - if (await user.TryNotify($"Your username contains a filtered word ({detectedBadWord.word}). Please change it before rejoining {guild.Name} Discord")) + if (await user.TryNotify($"Your username contains a filtered word ({detectedBadWord.Word}). Please change it before rejoining {guild.Name} Discord")) { - await gUser.KickAsync($"Username '{name}' triggered autofilter for '{detectedBadWord.word}'"); + await gUser.KickAsync($"Username '{name}' triggered autofilter for '{detectedBadWord.Word}'"); user.Id.AddWarn(1, "Username with filtered word", guild, null); return; }//If user's DMs are blocked @@ -104,7 +104,7 @@ public async Task CheckNameInGuild(IUser user, string name, SocketGuild guild) else if (guild.TryGetTextChannel(logSettings.pubLogChannel, out ITextChannel pubChannel)) await pubChannel.SendMessageAsync($"{user.Mention} your username contains a bad word but your DMs are closed. Please clean up your username before rejoining"); await Task.Delay(10000); - await gUser.KickAsync($"Username '{name}' triggered autofilter for '{detectedBadWord.word}'"); + await gUser.KickAsync($"Username '{name}' triggered autofilter for '{detectedBadWord.Word}'"); user.Id.AddWarn(1, "Username with filtered word (Note: DMs closed)", guild, null); } @@ -229,7 +229,7 @@ public async Task CheckMessage(SocketMessage message) } //Check for emojis - if (modSettings.badUEmojis.NotEmpty() && modSettings.badUEmojis.Any(s => message.Content.Contains(s))) + if (modSettings.badUEmojis?.Count is not null or 0 && modSettings.badUEmojis.Any(s => message.Content.Contains(s))) { await context.FilterPunish("Bad emoji used", modSettings, 0.8f); return; @@ -256,14 +256,14 @@ public async Task CheckMessage(SocketMessage message) BadWord detectedBadWord = msgContent.CheckForBadWords(badWords?.ToArray()); if (detectedBadWord != null) { - if (!string.IsNullOrEmpty(detectedBadWord.euphemism)) + if (!string.IsNullOrEmpty(detectedBadWord.Euphemism)) { - await context.FilterPunish("Bad word used (" + detectedBadWord.euphemism + ")", modSettings, detectedBadWord.size); + await context.FilterPunish("Bad word used (" + detectedBadWord.Euphemism + ")", modSettings, detectedBadWord.Size); return; } else { - await context.FilterPunish("Bad word used", modSettings, detectedBadWord.size); + await context.FilterPunish("Bad word used", modSettings, detectedBadWord.Size); return; } } diff --git a/BotCatMaxy/Components/Filter/FilterUtilities.cs b/BotCatMaxy/Components/Filter/FilterUtilities.cs index 0f7e9d7..5c09856 100644 --- a/BotCatMaxy/Components/Filter/FilterUtilities.cs +++ b/BotCatMaxy/Components/Filter/FilterUtilities.cs @@ -21,7 +21,7 @@ public static class FilterUtilities public static BadWord CheckForBadWords(this string message, BadWord[] badWords) { - if (badWords.IsNullOrEmpty()) return null; + if (badWords?.Length is null or 0) return null; //Checks for bad words StringBuilder sb = new StringBuilder(); @@ -67,9 +67,9 @@ public static BadWord CheckForBadWords(this string message, BadWord[] badWords) foreach (BadWord badWord in badWords) { - if (badWord.partOfWord) + if (badWord.PartOfWord) { - if (strippedMessage.Contains(badWord.word, StringComparison.InvariantCultureIgnoreCase)) + if (strippedMessage.Contains(badWord.Word, StringComparison.InvariantCultureIgnoreCase)) { return badWord; } @@ -78,7 +78,7 @@ public static BadWord CheckForBadWords(this string message, BadWord[] badWords) { //If bad word is ignored inside of words foreach (string word in messageParts) { - if (word.Equals(badWord.word, StringComparison.InvariantCultureIgnoreCase)) + if (word.Equals(badWord.Word, StringComparison.InvariantCultureIgnoreCase)) { return badWord; } diff --git a/BotCatMaxy/Components/Logging/DiscordLogging.cs b/BotCatMaxy/Components/Logging/DiscordLogging.cs index 14e3492..c222fcc 100644 --- a/BotCatMaxy/Components/Logging/DiscordLogging.cs +++ b/BotCatMaxy/Components/Logging/DiscordLogging.cs @@ -22,7 +22,7 @@ public static async Task LogMessage(string reason, IMessage message, IGu if (guild == null) { - guild = Utilities.GetGuild(message.Channel as SocketGuildChannel); + guild = (message.Channel as SocketGuildChannel).Guild; if (guild == null) { return null; @@ -51,7 +51,7 @@ public static async Task LogMessage(string reason, IMessage message, IGu } string links = ""; - if (message.Attachments.NotEmpty()) + if (message.Attachments?.Count is not null or 0) { foreach (IAttachment attachment in message.Attachments) { diff --git a/BotCatMaxy/Components/Moderation/ModerationCommands.cs b/BotCatMaxy/Components/Moderation/ModerationCommands.cs index 49b0679..c195a9f 100644 --- a/BotCatMaxy/Components/Moderation/ModerationCommands.cs +++ b/BotCatMaxy/Components/Moderation/ModerationCommands.cs @@ -77,11 +77,11 @@ public async Task DMUserWarnsAsync(UserRef userRef = null, int amount = 50) userRef = new UserRef(Context.Message.Author); var guildsEmbed = new EmbedBuilder(); - guildsEmbed.WithTitle("Reply with the the number next to the guild you want to check the infractions from"); + guildsEmbed.WithTitle("Reply with the number next to the guild you want to check the infractions from"); for (int i = 0; i < mutualGuilds.Length; i++) { - guildsEmbed.AddField($"[{i + 1}] {mutualGuilds[i].Name} discord", mutualGuilds[i].Id); + guildsEmbed.AddField($"[{i + 1}] {mutualGuilds[i].Name}", mutualGuilds[i].Id); } await ReplyAsync(embed: guildsEmbed.Build()); SocketGuild guild; @@ -105,10 +105,10 @@ public async Task DMUserWarnsAsync(UserRef userRef = null, int amount = 50) } List infractions = userRef.LoadInfractions(guild, false); - if (infractions.IsNullOrEmpty()) + if (infractions?.Count is null or 0) { string message = $"{userRef.Mention()} has no infractions"; - if (userRef.user == null) message += " or doesn't exist"; + if (userRef.User == null) message += " or doesn't exist"; await ReplyAsync(message); return; } @@ -126,7 +126,7 @@ public async Task CheckUserWarnsAsync(UserRef userRef = null, int amount = 5) { userRef ??= new UserRef(Context.User as SocketGuildUser); List infractions = userRef.LoadInfractions(Context.Guild, false); - if (infractions.IsNullOrEmpty()) + if (infractions?.Count is null or 0) { await ReplyAsync($"{userRef.Name()} has no infractions"); return; @@ -141,7 +141,7 @@ public async Task CheckUserWarnsAsync(UserRef userRef = null, int amount = 5) public async Task RemoveWarnAsync([RequireHierarchy] UserRef userRef, int index) { List infractions = userRef.LoadInfractions(Context.Guild, false); - if (infractions.IsNullOrEmpty()) + if (infractions?.Count is null or 0) { await ReplyAsync("Infractions are null"); return; @@ -151,11 +151,11 @@ public async Task RemoveWarnAsync([RequireHierarchy] UserRef userRef, int index) await ReplyAsync("Invalid infraction number"); return; } - string reason = infractions[index - 1].reason; + string reason = infractions[index - 1].Reason; infractions.RemoveAt(index - 1); userRef.SaveInfractions(infractions, Context.Guild); - await userRef.user?.TryNotify($"Your {index.Ordinalize()} warning in {Context.Guild.Name} discord for {reason} has been removed"); + await userRef.User?.TryNotify($"Your {index.Ordinalize()} warning in {Context.Guild.Name} discord for {reason} has been removed"); await ReplyAsync("Removed " + userRef.Mention() + "'s warning for " + reason); } @@ -195,15 +195,9 @@ public async Task KickAndWarn([RequireHierarchy] SocketGuildUser user, float siz [RequireContext(ContextType.Guild)] [RequireBotPermission(GuildPermission.BanMembers)] [RequireUserPermission(GuildPermission.KickMembers)] - public async Task TempBanUser([RequireHierarchy] UserRef userRef, string time, [Remainder] string reason) + public async Task TempBanUser([RequireHierarchy] UserRef userRef, TimeSpan time, [Remainder] string reason) { - var amount = time.ToTime(); - if (amount == null) - { - await ReplyAsync($"Unable to parse '{time}', be careful with decimals"); - return; - } - if (amount.Value.TotalMinutes < 1) + if (time.TotalMinutes < 1) { await ReplyAsync("Can't temp-ban for less than a minute"); return; @@ -211,23 +205,23 @@ public async Task TempBanUser([RequireHierarchy] UserRef userRef, string time, [ if (!(Context.Message.Author as SocketGuildUser).HasAdmin()) { ModerationSettings settings = Context.Guild.LoadFromFile(false); - if (settings?.maxTempAction != null && amount > settings.maxTempAction) + if (settings?.maxTempAction != null && time > settings.maxTempAction) { await ReplyAsync("You are not allowed to punish for that long"); return; } } TempActionList actions = Context.Guild.LoadFromFile(true); - TempAct oldAct = actions.tempBans.FirstOrDefault(tempMute => tempMute.user == userRef.ID); + TempAct oldAct = actions.tempBans.FirstOrDefault(tempMute => tempMute.User == userRef.ID); if (oldAct != null) { - if (!(Context.Message.Author as SocketGuildUser).HasAdmin() && (oldAct.length - (DateTime.UtcNow - oldAct.dateBanned)) >= amount) + if (!(Context.Message.Author as SocketGuildUser).HasAdmin() && (oldAct.Length - (DateTime.UtcNow - oldAct.DateBanned)) >= time) { await ReplyAsync($"{Context.User.Mention} please contact your admin(s) in order to shorten length of a punishment"); return; } IUserMessage query = await ReplyAsync( - $"{userRef.Name(true)} is already temp-banned for {oldAct.length.LimitedHumanize()} ({(oldAct.length - (DateTime.UtcNow - oldAct.dateBanned)).LimitedHumanize()} left), reply with !confirm within 2 minutes to confirm you want to change the length"); + $"{userRef.Name(true)} is already temp-banned for {oldAct.Length.LimitedHumanize()} ({(oldAct.Length - (DateTime.UtcNow - oldAct.DateBanned)).LimitedHumanize()} left), reply with !confirm within 2 minutes to confirm you want to change the length"); SocketMessage nextMessage = await NextMessageAsync(timeout: TimeSpan.FromMinutes(2)); if (nextMessage?.Content?.ToLower() == "!confirm") { @@ -244,8 +238,8 @@ public async Task TempBanUser([RequireHierarchy] UserRef userRef, string time, [ return; } } - await userRef.TempBan(amount.Value, reason, Context, actions); - Context.Message.DeleteOrRespond($"Temporarily banned {userRef.Mention()} for {amount.Value.LimitedHumanize(3)} because of {reason}", Context.Guild); + await userRef.TempBan(time, reason, Context, actions); + Context.Message.DeleteOrRespond($"Temporarily banned {userRef.Mention()} for {time.LimitedHumanize(3)} because of {reason}", Context.Guild); } [Command("tempbanwarn")] @@ -254,15 +248,9 @@ public async Task TempBanUser([RequireHierarchy] UserRef userRef, string time, [ [RequireContext(ContextType.Guild)] [RequireBotPermission(GuildPermission.BanMembers)] [RequireUserPermission(GuildPermission.KickMembers)] - public async Task TempBanWarnUser([RequireHierarchy] UserRef userRef, string time, [Remainder] string reason) + public async Task TempBanWarnUser([RequireHierarchy] UserRef userRef, TimeSpan time, [Remainder] string reason) { - var amount = time.ToTime(); - if (amount == null) - { - await ReplyAsync($"Unable to parse '{time}', be careful with decimals"); - return; - } - if (amount.Value.TotalMinutes < 1) + if (time.TotalMinutes < 1) { await ReplyAsync("Can't temp-ban for less than a minute"); return; @@ -270,7 +258,7 @@ public async Task TempBanWarnUser([RequireHierarchy] UserRef userRef, string tim if (!(Context.Message.Author as SocketGuildUser).HasAdmin()) { ModerationSettings settings = Context.Guild.LoadFromFile(false); - if (settings?.maxTempAction != null && amount > settings.maxTempAction) + if (settings?.maxTempAction != null && time > settings.maxTempAction) { await ReplyAsync("You are not allowed to punish for that long"); return; @@ -278,13 +266,13 @@ public async Task TempBanWarnUser([RequireHierarchy] UserRef userRef, string tim } await userRef.Warn(1, reason, Context.Channel as SocketTextChannel, "Discord"); TempActionList actions = Context.Guild.LoadFromFile(true); - if (actions.tempBans.Any(tempBan => tempBan.user == userRef.ID)) + if (actions.tempBans.Any(tempBan => tempBan.User == userRef.ID)) { Context.Message.DeleteOrRespond($"{userRef.Name()} is already temp-banned (the warn did go through)", Context.Guild); return; } - await userRef.TempBan(amount.Value, reason, Context, actions); - Context.Message.DeleteOrRespond($"Temporarily banned {userRef.Mention()} for {amount.Value.LimitedHumanize(3)} because of {reason}", Context.Guild); + await userRef.TempBan(time, reason, Context, actions); + Context.Message.DeleteOrRespond($"Temporarily banned {userRef.Mention()} for {time.LimitedHumanize(3)} because of {reason}", Context.Guild); } [Command("tempbanwarn")] @@ -293,15 +281,9 @@ public async Task TempBanWarnUser([RequireHierarchy] UserRef userRef, string tim [RequireContext(ContextType.Guild)] [RequireBotPermission(GuildPermission.BanMembers)] [RequireUserPermission(GuildPermission.KickMembers)] - public async Task TempBanWarnUser([RequireHierarchy] UserRef userRef, string time, float size, [Remainder] string reason) + public async Task TempBanWarnUser([RequireHierarchy] UserRef userRef, TimeSpan time, float size, [Remainder] string reason) { - var amount = time.ToTime(); - if (amount == null) - { - await ReplyAsync($"Unable to parse '{time}', be careful with decimals"); - return; - } - if (amount.Value.TotalMinutes < 1) + if (time.TotalMinutes < 1) { await ReplyAsync("Can't temp-ban for less than a minute"); return; @@ -309,7 +291,7 @@ public async Task TempBanWarnUser([RequireHierarchy] UserRef userRef, string tim if (!(Context.Message.Author as SocketGuildUser).HasAdmin()) { ModerationSettings settings = Context.Guild.LoadFromFile(false); - if (settings?.maxTempAction != null && amount > settings.maxTempAction) + if (settings?.maxTempAction != null && time > settings.maxTempAction) { await ReplyAsync("You are not allowed to punish for that long"); return; @@ -317,13 +299,13 @@ public async Task TempBanWarnUser([RequireHierarchy] UserRef userRef, string tim } await userRef.Warn(size, reason, Context.Channel as SocketTextChannel, "Discord"); TempActionList actions = Context.Guild.LoadFromFile(true); - if (actions.tempBans.Any(tempBan => tempBan.user == userRef.ID)) + if (actions.tempBans.Any(tempBan => tempBan.User == userRef.ID)) { await ReplyAsync($"{userRef.Name()} is already temp-banned (the warn did go through)"); return; } - await userRef.TempBan(amount.Value, reason, Context, actions); - Context.Message.DeleteOrRespond($"Temporarily banned {userRef.Mention()} for {amount.Value.LimitedHumanize(3)} because of {reason}", Context.Guild); + await userRef.TempBan(time, reason, Context, actions); + Context.Message.DeleteOrRespond($"Temporarily banned {userRef.Mention()} for {time.LimitedHumanize(3)} because of {reason}", Context.Guild); } [Command("tempmute", RunMode = RunMode.Async)] @@ -332,15 +314,9 @@ public async Task TempBanWarnUser([RequireHierarchy] UserRef userRef, string tim [RequireContext(ContextType.Guild)] [RequireBotPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.KickMembers)] - public async Task TempMuteUser([RequireHierarchy] UserRef userRef, string time, [Remainder] string reason) + public async Task TempMuteUser([RequireHierarchy] UserRef userRef, TimeSpan time, [Remainder] string reason) { - var amount = time.ToTime(); - if (amount == null) - { - await ReplyAsync($"Unable to parse '{time}', be careful with decimals"); - return; - } - if (amount.Value.TotalMinutes < 1) + if (time.TotalMinutes < 1) { await ReplyAsync("Can't temp-mute for less than a minute"); return; @@ -348,7 +324,7 @@ public async Task TempMuteUser([RequireHierarchy] UserRef userRef, string time, ModerationSettings settings = Context.Guild.LoadFromFile(); if (!(Context.Message.Author as SocketGuildUser).HasAdmin()) { - if (settings?.maxTempAction != null && amount > settings.maxTempAction) + if (settings?.maxTempAction != null && time > settings.maxTempAction) { await ReplyAsync("You are not allowed to punish for that long"); return; @@ -360,16 +336,16 @@ public async Task TempMuteUser([RequireHierarchy] UserRef userRef, string time, return; } TempActionList actions = Context.Guild.LoadFromFile(true); - TempAct oldAct = actions.tempMutes.FirstOrDefault(tempMute => tempMute.user == userRef.ID); + TempAct oldAct = actions.tempMutes.FirstOrDefault(tempMute => tempMute.User == userRef.ID); if (oldAct != null) { - if (!(Context.Message.Author as SocketGuildUser).HasAdmin() && (oldAct.length - (DateTime.UtcNow - oldAct.dateBanned)) >= amount) + if (!(Context.Message.Author as SocketGuildUser).HasAdmin() && (oldAct.Length - (DateTime.UtcNow - oldAct.DateBanned)) >= time) { await ReplyAsync($"{Context.User.Mention} please contact your admin(s) in order to shorten length of a punishment"); return; } IUserMessage query = await ReplyAsync( - $"{userRef.Name()} is already temp-muted for {oldAct.length.LimitedHumanize()} ({(oldAct.length - (DateTime.UtcNow - oldAct.dateBanned)).LimitedHumanize()} left), reply with !confirm within 2 minutes to confirm you want to change the length"); + $"{userRef.Name()} is already temp-muted for {oldAct.Length.LimitedHumanize()} ({(oldAct.Length - (DateTime.UtcNow - oldAct.DateBanned)).LimitedHumanize()} left), reply with !confirm within 2 minutes to confirm you want to change the length"); SocketMessage nextMessage = await NextMessageAsync(timeout: TimeSpan.FromMinutes(2)); if (nextMessage?.Content?.ToLower() == "!confirm") { @@ -387,8 +363,8 @@ public async Task TempMuteUser([RequireHierarchy] UserRef userRef, string time, } } - await userRef.TempMute(amount.Value, reason, Context, settings, actions); - Context.Message.DeleteOrRespond($"Temporarily muted {userRef.Mention()} for {amount.Value.LimitedHumanize(3)} because of {reason}", Context.Guild); + await userRef.TempMute(time, reason, Context, settings, actions); + Context.Message.DeleteOrRespond($"Temporarily muted {userRef.Mention()} for {time.LimitedHumanize(3)} because of {reason}", Context.Guild); } [Command("tempmutewarn")] @@ -397,15 +373,9 @@ public async Task TempMuteUser([RequireHierarchy] UserRef userRef, string time, [RequireContext(ContextType.Guild)] [RequireBotPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.KickMembers)] - public async Task TempMuteWarnUser([RequireHierarchy] UserRef userRef, string time, [Remainder] string reason) + public async Task TempMuteWarnUser([RequireHierarchy] UserRef userRef, TimeSpan time, [Remainder] string reason) { - var amount = time.ToTime(); - if (amount == null) - { - await ReplyAsync($"Unable to parse '{time}', be careful with decimals"); - return; - } - if (amount.Value.TotalMinutes < 1) + if (time.TotalMinutes < 1) { await ReplyAsync("Can't temp-mute for less than a minute"); return; @@ -413,7 +383,7 @@ public async Task TempMuteWarnUser([RequireHierarchy] UserRef userRef, string ti ModerationSettings settings = Context.Guild.LoadFromFile(); if (!(Context.Message.Author as SocketGuildUser).HasAdmin()) { - if (settings?.maxTempAction != null && amount > settings.maxTempAction) + if (settings?.maxTempAction != null && time > settings.maxTempAction) { await ReplyAsync("You are not allowed to punish for that long"); return; @@ -424,16 +394,16 @@ public async Task TempMuteWarnUser([RequireHierarchy] UserRef userRef, string ti await ReplyAsync("Muted role is null or invalid"); return; } - await userRef.Warn(1, reason, Context.Channel as SocketTextChannel, "Discord"); + await userRef.Warn(1, reason, Context.Channel as SocketTextChannel, Context.Message.GetJumpUrl()); TempActionList actions = Context.Guild.LoadFromFile(true); - if (actions.tempMutes.Any(tempMute => tempMute.user == userRef.ID)) + if (actions.tempMutes.Any(tempMute => tempMute.User == userRef.ID)) { await ReplyAsync($"{userRef.Name()} is already temp-muted, (the warn did go through)"); return; } - await userRef.TempMute(amount.Value, reason, Context, settings, actions); - Context.Message.DeleteOrRespond($"Temporarily muted {userRef.Mention()} for {amount.Value.LimitedHumanize(3)} because of {reason}", Context.Guild); + await userRef.TempMute(time, reason, Context, settings, actions); + Context.Message.DeleteOrRespond($"Temporarily muted {userRef.Mention()} for {time.LimitedHumanize(3)} because of {reason}", Context.Guild); } [Command("tempmutewarn")] @@ -442,15 +412,9 @@ public async Task TempMuteWarnUser([RequireHierarchy] UserRef userRef, string ti [RequireContext(ContextType.Guild)] [RequireBotPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.KickMembers)] - public async Task TempMuteWarnUser([RequireHierarchy] UserRef userRef, string time, float size, [Remainder] string reason) + public async Task TempMuteWarnUser([RequireHierarchy] UserRef userRef, TimeSpan time, float size, [Remainder] string reason) { - var amount = time.ToTime(); - if (amount == null) - { - await ReplyAsync($"Unable to parse '{time}', be careful with decimals"); - return; - } - if (amount.Value.TotalMinutes < 1) + if (time.TotalMinutes < 1) { await ReplyAsync("Can't temp-mute for less than a minute"); return; @@ -458,7 +422,7 @@ public async Task TempMuteWarnUser([RequireHierarchy] UserRef userRef, string ti ModerationSettings settings = Context.Guild.LoadFromFile(); if (!(Context.Message.Author as SocketGuildUser).HasAdmin()) { - if (settings?.maxTempAction != null && amount > settings.maxTempAction) + if (settings?.maxTempAction != null && time > settings.maxTempAction) { await ReplyAsync("You are not allowed to punish for that long"); return; @@ -471,14 +435,14 @@ public async Task TempMuteWarnUser([RequireHierarchy] UserRef userRef, string ti } await userRef.Warn(size, reason, Context.Channel as SocketTextChannel, "Discord"); TempActionList actions = Context.Guild.LoadFromFile(true); - if (actions.tempMutes.Any(tempMute => tempMute.user == userRef.ID)) + if (actions.tempMutes.Any(tempMute => tempMute.User == userRef.ID)) { await ReplyAsync($"{userRef.Name()} is already temp-muted, (the warn did go through)"); return; } - await userRef.TempMute(amount.Value, reason, Context, settings, actions); - Context.Message.DeleteOrRespond($"Temporarily muted {userRef.Mention()} for {amount.Value.LimitedHumanize(3)} because of {reason}", Context.Guild); + await userRef.TempMute(time, reason, Context, settings, actions); + Context.Message.DeleteOrRespond($"Temporarily muted {userRef.Mention()} for {time.LimitedHumanize(3)} because of {reason}", Context.Guild); } [Command("ban", RunMode = RunMode.Async)] @@ -488,25 +452,37 @@ public async Task TempMuteWarnUser([RequireHierarchy] UserRef userRef, string ti [RequireUserPermission(GuildPermission.BanMembers)] public async Task Ban([RequireHierarchy] UserRef userRef, [Remainder] string reason) { + if (reason.Split(' ').First().ToTime() != null) + { + var query = await ReplyAsync("Are you sure you don't mean to use !tempban? Reply with !confirm if you want to override"); + var reply = await NextMessageAsync(timeout: TimeSpan.FromMinutes(2)); + if (reply?.Content?.ToLower() != "!confirm") + { + Context.Channel.DeleteMessageAsync(query); + ReplyAsync("Command Canceled"); + return; + } + } + TempActionList actions = Context.Guild.LoadFromFile(false); - if (actions?.tempBans?.Any(tempBan => tempBan.user == userRef.ID) ?? false) + if (actions?.tempBans?.Any(tempBan => tempBan.User == userRef.ID) ?? false) { - var query = await ReplyAsync("User is already tempbanned. Reply with !confirm if you want to override?"); - var reply = await NextMessageAsync(timeout: TimeSpan.FromMinutes(5)); + var query = await ReplyAsync("User is already tempbanned. Reply with !confirm if you want to override"); + var reply = await NextMessageAsync(timeout: TimeSpan.FromMinutes(2)); if (reply?.Content?.ToLower() != "!confirm") { Context.Channel.DeleteMessageAsync(query); ReplyAsync("Command Canceled"); return; } - actions.tempBans.Remove(actions.tempBans.First(tempban => tempban.user == userRef.ID)); + actions.tempBans.Remove(actions.tempBans.First(tempban => tempban.User == userRef.ID)); } else if ((await Context.Guild.GetBansAsync()).Any(ban => ban.User.Id == userRef.ID)) { await ReplyAsync("User has already been banned permanently"); return; } - await userRef.user?.TryNotify($"You have been perm banned in the {Context.Guild.Name} discord for {reason}"); + await userRef.User?.TryNotify($"You have been perm banned in the {Context.Guild.Name} discord for {reason}"); await Context.Guild.AddBanAsync(userRef.ID, reason: reason); DiscordLogging.LogTempAct(Context.Guild, Context.Message.Author, userRef, "Bann", reason, Context.Message.GetJumpUrl(), TimeSpan.Zero); Context.Message.DeleteOrRespond($"{userRef.Name(true)} has been banned for {reason}", Context.Guild); @@ -523,7 +499,7 @@ public async Task DeleteMany(uint number, UserRef user = null) await ReplyAsync("Invalid number"); return; } - if (user?.gUser != null && user.gUser.Hierarchy >= ((SocketGuildUser)Context.User).Hierarchy) + if (user?.GuildUser != null && user.GuildUser.Hierarchy >= ((SocketGuildUser)Context.User).Hierarchy) { await ReplyAsync("Can't target deleted messages belonging to people with higher hierarchy"); return; diff --git a/BotCatMaxy/Components/Moderation/PunishFunctions.cs b/BotCatMaxy/Components/Moderation/PunishFunctions.cs index 5255880..17ee4f7 100644 --- a/BotCatMaxy/Components/Moderation/PunishFunctions.cs +++ b/BotCatMaxy/Components/Moderation/PunishFunctions.cs @@ -40,10 +40,10 @@ public static class PunishFunctions public static async Task Warn(this UserRef userRef, float size, string reason, SocketTextChannel channel, string logLink = null) { Contract.Requires(userRef != null); - if (userRef.gUser != null) - return await userRef.gUser.Warn(size, reason, channel, logLink); + if (userRef.GuildUser != null) + return await userRef.GuildUser.Warn(size, reason, channel, logLink); else - return await userRef.ID.Warn(size, reason, channel, userRef.user, logLink); + return await userRef.ID.Warn(size, reason, channel, userRef.User, logLink); } public static async Task Warn(this SocketGuildUser user, float size, string reason, SocketTextChannel channel, string logLink = null) @@ -102,11 +102,11 @@ public static List AddWarn(this ulong userID, float size, string rea List infractions = userID.LoadInfractions(guild, true); Infraction newInfraction = new Infraction { - reason = reason, - time = DateTime.UtcNow, - size = size + Reason = reason, + Time = DateTime.UtcNow, + Size = size, + LogLink = logLink }; - if (!string.IsNullOrEmpty(logLink)) newInfraction.logLink = logLink; infractions.Add(newInfraction); userID.SaveInfractions(guild, infractions); return infractions; @@ -144,37 +144,37 @@ public InfractionInfo(List infractions, int amount = 5, bool showLin Infraction infraction = infractions[i]; //Gets how long ago all the infractions were - TimeSpan dateAgo = DateTime.UtcNow.Subtract(infraction.time); - totalInfractions.sum += infraction.size; + TimeSpan dateAgo = DateTime.UtcNow.Subtract(infraction.Time); + totalInfractions.sum += infraction.Size; totalInfractions.count++; if (dateAgo.Days <= 7) { - infractions7Days.sum += infraction.size; + infractions7Days.sum += infraction.Size; infractions7Days.count++; } if (dateAgo.Days <= 30) { - infractions30Days.sum += infraction.size; + infractions30Days.sum += infraction.Size; infractions30Days.count++; if (dateAgo.Days < 1) { - infractionsToday.sum += infraction.size; + infractionsToday.sum += infraction.Size; infractionsToday.count++; } } string size = ""; - if (infraction.size != 1) + if (infraction.Size != 1) { - size = "(" + infraction.size + "x) "; + size = "(" + infraction.Size + "x) "; } if (n < amount) { string jumpLink = ""; string timeAgo = dateAgo.LimitedHumanize(2); - if (showLinks && !infraction.logLink.IsNullOrEmpty()) jumpLink = $" [[Logged Here]({infraction.logLink})]"; - string infracInfo = $"[{MathF.Abs(i - infractions.Count)}] {size}{infraction.reason}{jumpLink} - {timeAgo}"; + if (showLinks && !infraction.LogLink.IsNullOrEmpty()) jumpLink = $" [[Logged Here]({infraction.LogLink})]"; + string infracInfo = $"[{MathF.Abs(i - infractions.Count)}] {size}{infraction.Reason}{jumpLink} - {timeAgo}"; n++; //So we don't go over embed character limit of 9000 @@ -183,8 +183,8 @@ public InfractionInfo(List infractions, int amount = 5, bool showLin if ((infractionStrings.LastOrDefault() + infracInfo).Length < 1024) { - if (infractionStrings.LastOrDefault().NotEmpty()) infractionStrings[infractionStrings.Count - 1] += "\n"; - infractionStrings[infractionStrings.Count - 1] += infracInfo; + if (infractionStrings.LastOrDefault()?.Length is not null or 0) infractionStrings[infractionStrings.Count - 1] += "\n"; + infractionStrings[^1] += infracInfo; } else { @@ -230,11 +230,11 @@ public static async Task TempBan(this UserRef userRef, TimeSpan time, string rea actions.SaveToFile(); await context.Guild.AddBanAsync(userRef.ID, reason: reason); DiscordLogging.LogTempAct(context.Guild, context.User, userRef, "bann", reason, context.Message.GetJumpUrl(), time); - if (userRef.user != null) + if (userRef.User != null) { try { - await userRef.user.Notify($"tempbanned for {time.LimitedHumanize()}", reason, context.Guild, context.Message.Author); + await userRef.User.Notify($"tempbanned for {time.LimitedHumanize()}", reason, context.Guild, context.Message.Author); } catch (Exception e) { @@ -250,13 +250,13 @@ public static async Task TempMute(this UserRef userRef, TimeSpan time, string re if (actions == null) actions = context.Guild.LoadFromFile(true); actions.tempMutes.Add(tempMute); actions.SaveToFile(); - await userRef.gUser?.AddRoleAsync(context.Guild.GetRole(settings.mutedRole)); + await userRef.GuildUser?.AddRoleAsync(context.Guild.GetRole(settings.mutedRole)); DiscordLogging.LogTempAct(context.Guild, context.User, userRef, "mut", reason, context.Message.GetJumpUrl(), time); - if (userRef.user != null) + if (userRef.User != null) { try { - await userRef.user?.Notify($"tempmuted for {time.LimitedHumanize()}", reason, context.Guild, context.Message.Author); + await userRef.User?.Notify($"tempmuted for {time.LimitedHumanize()}", reason, context.Guild, context.Message.Author); } catch (Exception e) { diff --git a/BotCatMaxy/Components/ReportModule.cs b/BotCatMaxy/Components/ReportModule.cs index 59b5f88..e9faa6e 100644 --- a/BotCatMaxy/Components/ReportModule.cs +++ b/BotCatMaxy/Components/ReportModule.cs @@ -99,7 +99,7 @@ public async Task Report() embed.WithFooter("User ID: " + Context.Message.Author.Id); embed.WithCurrentTimestamp(); string links = ""; - if (reportMsg.Attachments.NotEmpty()) + if (reportMsg.Attachments?.Count is not null or 0) links = reportMsg.Attachments.Select(attachment => attachment.ProxyUrl).ListItems(" "); var channel = guild.GetTextChannel(settings.channelID.Value); await channel.SendMessageAsync(embed: embed.Build()); diff --git a/BotCatMaxy/Components/Slowmode/SlowmodeCommands.cs b/BotCatMaxy/Components/Slowmode/SlowmodeCommands.cs index aab888a..f2487b8 100644 --- a/BotCatMaxy/Components/Slowmode/SlowmodeCommands.cs +++ b/BotCatMaxy/Components/Slowmode/SlowmodeCommands.cs @@ -26,21 +26,15 @@ public async Task SetSlowMode(int time) [Command("setslowmode"), Alias("setcooldown", "slowmodeset")] [Summary("Sets this channel's slowmode.")] - public async Task SetSlowMode(string time) + public async Task SetSlowMode(TimeSpan time) { - var amount = time.ToTime(); - if (amount == null) - { - await ReplyAsync($"Unable to parse '{time.StrippedOfPing()}', be careful with decimals"); - return; - } - if (amount.Value.TotalSeconds % 1 != 0) + if (time.TotalSeconds % 1 != 0) { await ReplyAsync("Can't set slowmode precision for less than a second"); return; } - await (Context.Channel as SocketTextChannel).ModifyAsync(channel => channel.SlowModeInterval = (int)amount.Value.TotalSeconds); - await ReplyAsync($"Set channel slowmode to {amount.Value.LimitedHumanize()}"); + await (Context.Channel as SocketTextChannel).ModifyAsync(channel => channel.SlowModeInterval = time.Seconds); + await ReplyAsync($"Set channel slowmode to {time.LimitedHumanize()}"); } [Command("dynamicslowmode"), Alias("ds"), Priority(5)] diff --git a/BotCatMaxy/Misc Commands.cs b/BotCatMaxy/Misc Commands.cs index c6b66c2..e1f5240 100644 --- a/BotCatMaxy/Misc Commands.cs +++ b/BotCatMaxy/Misc Commands.cs @@ -228,7 +228,7 @@ public async Task Statistics() { foreach (Infraction infraction in BsonSerializer.Deserialize(doc).infractions) { - if (DateTime.UtcNow - infraction.time < TimeSpan.FromHours(24)) + if (DateTime.UtcNow - infraction.Time < TimeSpan.FromHours(24)) infractions24Hours++; totalInfractons++; } @@ -270,7 +270,7 @@ public async Task ActSanityCheck() TempActionList actions = sockGuild.LoadFromFile(false); if (actions != null) { - if (!actions.tempBans.IsNullOrEmpty()) + if (actions.tempBans?.Count is null or 0) { foreach (TempAct tempBan in actions.tempBans) { @@ -282,7 +282,7 @@ public async Task ActSanityCheck() } ModerationSettings settings = sockGuild.LoadFromFile(); - if (settings != null && sockGuild.GetRole(settings.mutedRole) != null && actions.tempMutes.NotEmpty()) + if (settings is not null && sockGuild.GetRole(settings.mutedRole) != null && actions.tempMutes?.Count is not null or 0) { foreach (TempAct tempMute in actions.tempMutes) { @@ -304,7 +304,7 @@ public async Task ActSanityCheck() embed.Title = $"{tempActsToEnd.Count} tempacts should've ended (longest one ended ago is {TimeSpan.FromMilliseconds(tempActsToEnd.Select(tempAct => DateTime.UtcNow.Subtract(tempAct.End).TotalMilliseconds).Max()).Humanize(2)}"; foreach (TypedTempAct tempAct in tempActsToEnd) { - embed.AddField($"{tempAct.type} started on {tempAct.dateBanned.ToShortTimeString()} {tempAct.dateBanned.ToShortDateString()} for {tempAct.length.LimitedHumanize()}", + embed.AddField($"{tempAct.Type} started on {tempAct.DateBanned.ToShortTimeString()} {tempAct.DateBanned.ToShortDateString()} for {tempAct.Length.LimitedHumanize()}", $"Should've ended {DateTime.UtcNow.Subtract(tempAct.End).LimitedHumanize()}"); } await ReplyAsync(embed: embed.Build()); diff --git a/BotCatMaxy/Models/BadWord.cs b/BotCatMaxy/Models/BadWord.cs index 5d8d597..b949740 100644 --- a/BotCatMaxy/Models/BadWord.cs +++ b/BotCatMaxy/Models/BadWord.cs @@ -8,21 +8,17 @@ namespace BotCatMaxy.Models { [BsonIgnoreExtraElements] - public class BadWord + public record BadWord { - public string word; - public string euphemism; - public float size = 0.5f; - public bool partOfWord = true; - public object moreWords; + public string Word { get; init; } + public string Euphemism { get; init; } + public float Size { get; init; } = 0.5f; + public bool PartOfWord { get; init; } = true; } - [BsonIgnoreExtraElements] public class BadWordList : DataObject { - [BsonId] - public string Id = "BadWordList"; - public List badWords = new List(); + public List badWords = new(); } public class BadWords @@ -43,10 +39,10 @@ public BadWords(IGuild guild) foreach (BadWord badWord in all) { - if (badWord.partOfWord) insideWords.Add(badWord); + if (badWord.PartOfWord) insideWords.Add(badWord); else onlyAlone.Add(badWord); - List group = grouped.Find(list => list.FirstOrDefault() != null && list.First().euphemism == badWord.euphemism); + List group = grouped.Find(list => list.FirstOrDefault() != null && list.First().Euphemism == badWord.Euphemism); if (group != null) { group.Add(badWord); diff --git a/BotCatMaxy/Models/Infraction.cs b/BotCatMaxy/Models/Infraction.cs index 445eff9..54707d8 100644 --- a/BotCatMaxy/Models/Infraction.cs +++ b/BotCatMaxy/Models/Infraction.cs @@ -1,23 +1,23 @@ using MongoDB.Bson.Serialization.Attributes; using System; -using System.Collections.Generic; +using System.Collections.Generic ; namespace BotCatMaxy.Models { - public class Infraction + public record Infraction { [BsonDateTimeOptions(Kind = DateTimeKind.Utc)] - public DateTime time; - public string logLink; - public string reason; - public float size; + public DateTime Time { get; init; } + public string LogLink { get; init; } + public string Reason { get; init; } + public float Size { get; init; } } [BsonIgnoreExtraElements] public class UserInfractions { [BsonId] - public ulong ID = 0; - public List infractions = new List(); + public ulong ID { get; init; } + public List infractions = new(); } } diff --git a/BotCatMaxy/Models/LogSettings.cs b/BotCatMaxy/Models/LogSettings.cs index f3fe0c4..9fbf5c4 100644 --- a/BotCatMaxy/Models/LogSettings.cs +++ b/BotCatMaxy/Models/LogSettings.cs @@ -3,11 +3,10 @@ namespace BotCatMaxy.Models { - [BsonIgnoreExtraElements] public class LogSettings : DataObject { [BsonId] - public string Id = "LogSettings"; + public const string Id = "LogSettings"; public ulong? pubLogChannel = null; public ulong? logChannel = null; public bool logDeletes = true; diff --git a/BotCatMaxy/Models/ModerationSettings.cs b/BotCatMaxy/Models/ModerationSettings.cs index 4e8b9ba..57ee6db 100644 --- a/BotCatMaxy/Models/ModerationSettings.cs +++ b/BotCatMaxy/Models/ModerationSettings.cs @@ -1,26 +1,22 @@ using BotCatMaxy.Data; -using MongoDB.Bson.Serialization.Attributes; using System; using System.Collections.Generic; namespace BotCatMaxy.Models { - [BsonIgnoreExtraElements] public class ModerationSettings : DataObject { - [BsonId] - public string Id = "ModerationSettings"; - public List ableToWarn = new List(); - public List cantBeWarned = new List(); - public List channelsWithoutAutoMod = new List(); - public HashSet allowedLinks = new HashSet(); - public List allowedToLink = new List(); - public HashSet badUEmojis = new HashSet(); - public HashSet badLinks = new HashSet(); - public List ableToBan = new List(); - public List anouncementChannels = new List(); - public Dictionary dynamicSlowmode = new Dictionary(); - public List whitelistedForInvite = new List(); + public List ableToWarn = new(); + public List cantBeWarned = new(); + public List channelsWithoutAutoMod = new(); + public HashSet allowedLinks = new(); + public List allowedToLink = new(); + public HashSet badUEmojis = new(); + public HashSet badLinks = new(); + public List ableToBan = new(); + public List anouncementChannels = new(); + public Dictionary dynamicSlowmode = new(); + public List whitelistedForInvite = new(); public TimeSpan? maxTempAction = null; public ulong mutedRole = 0; public ushort allowedCaps = 0; @@ -31,4 +27,4 @@ public class ModerationSettings : DataObject public bool moderateNames = false; public ushort? maxNewLines = null; } -} +} \ No newline at end of file diff --git a/BotCatMaxy/Models/ReportSettings.cs b/BotCatMaxy/Models/ReportSettings.cs index 997eb11..d913533 100644 --- a/BotCatMaxy/Models/ReportSettings.cs +++ b/BotCatMaxy/Models/ReportSettings.cs @@ -6,8 +6,6 @@ namespace BotCatMaxy.Models { public class ReportSettings : DataObject { - [BsonId] - public string Id = "ReportSettings"; public TimeSpan? cooldown; public ulong? channelID; public ulong? requiredRole; diff --git a/BotCatMaxy/Models/TempAction.cs b/BotCatMaxy/Models/TempAction.cs index 725ef78..96c65be 100644 --- a/BotCatMaxy/Models/TempAction.cs +++ b/BotCatMaxy/Models/TempAction.cs @@ -5,30 +5,30 @@ namespace BotCatMaxy.Models { - public class TempAct + public record TempAct { public TempAct(ulong userID, TimeSpan length, string reason) { - user = userID; - this.reason = reason; - dateBanned = DateTime.UtcNow; - this.length = length; + User = userID; + Reason = reason; + DateBanned = DateTime.UtcNow; + Length = length; } public TempAct(UserRef userRef, TimeSpan length, string reason) { - user = userRef.ID; - this.reason = reason; - dateBanned = DateTime.UtcNow; - this.length = length; + User = userRef.ID; + Reason = reason; + DateBanned = DateTime.UtcNow; + Length = length; } - public DateTime End => dateBanned.Add(length); + public DateTime End => DateBanned.Add(Length); - public string reason; - public ulong user; - public TimeSpan length; + public string Reason { get; init; } + public ulong User { get; init; } + public TimeSpan Length { get; init; } [BsonDateTimeOptions(Kind = DateTimeKind.Utc)] - public DateTime dateBanned; + public DateTime DateBanned { get; init; } } public enum TempActType @@ -39,20 +39,18 @@ public enum TempActType public class TempActionList : DataObject { - [BsonId] - public string ID = "TempActionList"; - public List tempBans = new List(); - public List tempMutes = new List(); + public List tempBans = new(); + public List tempMutes = new(); } - public class TypedTempAct : TempAct + public record TypedTempAct : TempAct { - public TempActType type; + public TempActType Type { get; } - public TypedTempAct(TempAct tempAct, TempActType type) : base(tempAct.user, tempAct.length, tempAct.reason) + public TypedTempAct(TempAct tempAct, TempActType type) : base(tempAct.User, tempAct.Length, tempAct.Reason) { - dateBanned = tempAct.dateBanned; - this.type = type; + DateBanned = tempAct.DateBanned; + Type = type; } } } diff --git a/BotCatMaxy/Models/UserActs.cs b/BotCatMaxy/Models/UserActs.cs index 7885876..6e1d3b5 100644 --- a/BotCatMaxy/Models/UserActs.cs +++ b/BotCatMaxy/Models/UserActs.cs @@ -17,7 +17,7 @@ public class ActRecord public class UserActs { [BsonId] - public ulong ID = 0; - public List acts = new List(); + public ulong ID { get; init; } + public List acts = new(); } } diff --git a/BotCatMaxy/Models/UserRef.cs b/BotCatMaxy/Models/UserRef.cs index 7c7902a..cb9d6c3 100644 --- a/BotCatMaxy/Models/UserRef.cs +++ b/BotCatMaxy/Models/UserRef.cs @@ -2,22 +2,22 @@ namespace BotCatMaxy.Models { - public class UserRef + public record UserRef { - public readonly SocketGuildUser gUser; - public readonly SocketUser user; - public readonly ulong ID; + public SocketGuildUser GuildUser { get; init; } + public SocketUser User { get; init; } + public ulong ID { get; init; } public UserRef(SocketGuildUser gUser) { - this.gUser = gUser; - user = gUser; + GuildUser = gUser; + User = gUser; ID = gUser.Id; } public UserRef(SocketUser user) { - this.user = user; + User = user; ID = user.Id; } @@ -25,9 +25,9 @@ public UserRef(SocketUser user) public UserRef(UserRef userRef, SocketGuild guild) { - user = userRef.user; + User = userRef.User; ID = userRef.ID; - gUser = guild.GetUser(ID); + GuildUser = guild.GetUser(ID); } } } diff --git a/BotCatMaxy/Properties/Tokens.targets.template b/BotCatMaxy/Properties/Tokens.targets.template new file mode 100644 index 0000000..52605cc --- /dev/null +++ b/BotCatMaxy/Properties/Tokens.targets.template @@ -0,0 +1,23 @@ + + + + + + Value1D + Value2D + + + + + Value1R + Value2R + + + + + +DataToken=$(DataToken) +DiscordToken=$(DiscordToken) + + + \ No newline at end of file diff --git a/BotCatMaxy/Startup/Command Handler.cs b/BotCatMaxy/Startup/Command Handler.cs index 0e251d7..8ee134f 100644 --- a/BotCatMaxy/Startup/Command Handler.cs +++ b/BotCatMaxy/Startup/Command Handler.cs @@ -54,6 +54,7 @@ public async Task InstallCommandsAsync() _commands.AddTypeReader(typeof(Emoji), new EmojiTypeReader()); _commands.AddTypeReader(typeof(UserRef), new UserRefTypeReader()); _commands.AddTypeReader(typeof(IUser), new BetterUserTypeReader()); + _commands.AddTypeReader(typeof(TimeSpan), new TimeSpanTypeReader(), true); // See Dependency Injection guide for more information. await _commands.AddModulesAsync(assembly: Assembly.GetEntryAssembly(), @@ -193,10 +194,10 @@ public override async Task CheckPermissionsAsync(ICommandCon return PreconditionResult.FromError("This command cannot be used outside of a guild"); var targetUser = value switch { - UserRef userRef => userRef.gUser as SocketGuildUser, + UserRef userRef => userRef.GuildUser as SocketGuildUser, SocketGuildUser targetGuildUser => targetGuildUser, ulong userId => await context.Guild.GetUserAsync(userId).ConfigureAwait(false) as SocketGuildUser, - _ => throw new ArgumentOutOfRangeException("Unkown Type used in parameter that requires hierarchy"), + _ => throw new ArgumentOutOfRangeException("Unknown Type used in parameter that requires hierarchy"), }; if (targetUser == null) if (value is UserRef) diff --git a/BotCatMaxy/Startup/Main.cs b/BotCatMaxy/Startup/Main.cs index 7c074e7..2ea5c3e 100644 --- a/BotCatMaxy/Startup/Main.cs +++ b/BotCatMaxy/Startup/Main.cs @@ -9,7 +9,11 @@ using Serilog.Sinks.SystemConsole.Themes; using System; using System.Globalization; +using System.IO; +using System.Linq; using System.Reflection; +using System.Text; +using System.Text.Json; using System.Threading.Tasks; namespace BotCatMaxy @@ -20,17 +24,22 @@ public class MainClass public static MongoClient dbClient; public static async Task Main(string[] args) { + var baseDir = AppDomain.CurrentDomain.BaseDirectory; var logConfig = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.Console(theme: AnsiConsoleTheme.Code) - .WriteTo.File($"{AppDomain.CurrentDomain.BaseDirectory}/log.txt", rollingInterval: RollingInterval.Day); + .WriteTo.File($"{baseDir}/log.txt", rollingInterval: RollingInterval.Day); + ExceptionLogging.logger = logConfig.CreateLogger(); + await new LogMessage(LogSeverity.Info, "App", $"Starting with logging at {baseDir}").Log(); + + await DataManipulator.MapTypes(); #if DEBUG - logConfig.WriteTo.File($"C:/Users/bobth/Documents/Bmax-test/log.txt", rollingInterval: RollingInterval.Day); BotInfo.debug = true; - dbClient = new MongoClient(HiddenInfo.debugDB); #endif - ExceptionLogging.logger = logConfig.CreateLogger(); - await new LogMessage(LogSeverity.Info, "Log", $"Program log logging at {AppDomain.CurrentDomain.BaseDirectory}").Log(); + DotNetEnv.Env.Load("BotCatMaxy.env"); + var dbKey = Environment.GetEnvironmentVariable("DataToken"); + dbClient = new MongoClient(); + var config = new DiscordSocketConfig { AlwaysDownloadUsers = true, //going to keep here for new guilds added, but seems to be broken for startup per https://github.com/discord-net/Discord.Net/issues/1646 @@ -38,12 +47,10 @@ public static async Task Main(string[] args) MessageCacheSize = 120, ExclusiveBulkDelete = false, DefaultRetryMode = RetryMode.AlwaysRetry, - GatewayIntents = GatewayIntents.GuildBans | GatewayIntents.GuildMembers | GatewayIntents.GuildMessageReactions | GatewayIntents.GuildMessages | GatewayIntents.DirectMessages | GatewayIntents.Guilds + GatewayIntents = GatewayIntents.GuildBans | GatewayIntents.GuildMembers | + GatewayIntents.GuildMessageReactions | GatewayIntents.GuildMessages | GatewayIntents.DirectMessages | GatewayIntents.Guilds }; - //Maps all the classes - _ = DataManipulator.MapTypes(); - //Sets up the events _client = new DiscordSocketClient(config); _client.Log += ExceptionLogging.Log; @@ -52,22 +59,7 @@ public static async Task Main(string[] args) //Delete once https://github.com/discord-net/Discord.Net/issues/1646 is fixed _client.GuildAvailable += HandleGuildAvailable; - if (args.Length > 1 && args[1].NotEmpty() && args[1].ToLower() == "canary") - { - await _client.LoginAsync(TokenType.Bot, HiddenInfo.testToken); - dbClient = new MongoClient(HiddenInfo.debugDB); - BotInfo.debug = true; - } - else - { -#if DEBUG - await _client.LoginAsync(TokenType.Bot, HiddenInfo.testToken); -#else - dbClient ??= new MongoClient(HiddenInfo.mainDB); - await _client.LoginAsync(TokenType.Bot, HiddenInfo.mainToken); -#endif - } - await new LogMessage(LogSeverity.Info, "Mongo", $"Connected to cluster {dbClient.Cluster.ClusterId} with {dbClient.ListDatabases().ToList().Count} databases").Log(); + await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("DiscordToken")); await _client.StartAsync(); SettingsCache cacher = new SettingsCache(_client); @@ -89,17 +81,11 @@ public static async Task Main(string[] args) } } - StatusManager statusManager; - if (args.Length > 0 && args[0].NotEmpty()) - { - await new LogMessage(LogSeverity.Info, "Main", $"Starting with version {args[0]}, built {buildDate.ToShortDateString()}, {(DateTime.UtcNow - buildDate).LimitedHumanize()} ago").Log(); - statusManager = new StatusManager(_client, args[0]); - } - else - { - await new LogMessage(LogSeverity.Info, "Main", $"Starting with no version num, built {buildDate.ToShortDateString()}, {(DateTime.UtcNow - buildDate).LimitedHumanize()} ago").Log(); - statusManager = new StatusManager(_client, "unknown"); - } + string version = args.ElementAtOrDefault(0) ?? "unknown"; + await new LogMessage(LogSeverity.Info, "Main", $"Starting with version {version}, built {buildDate.ToShortDateString()}, {(DateTime.UtcNow - buildDate).LimitedHumanize()} ago").Log(); + StatusManager statusManager = new StatusManager(_client, version); + + await new LogMessage(LogSeverity.Info, "Mongo", $"Connected to cluster {dbClient.Cluster.ClusterId} with {dbClient.ListDatabases().ToList().Count} databases").Log(); var serviceConfig = new CommandServiceConfig { diff --git a/BotCatMaxy/Startup/TempActions.cs b/BotCatMaxy/Startup/TempActions.cs index c946e70..51eaf3a 100644 --- a/BotCatMaxy/Startup/TempActions.cs +++ b/BotCatMaxy/Startup/TempActions.cs @@ -5,6 +5,8 @@ using Discord; using Discord.Rest; using Discord.WebSocket; +using Humanizer; +using Polly; using System; using System.Collections.Generic; using System.Linq; @@ -18,7 +20,7 @@ public class TempActions readonly DiscordSocketClient client; public static CurrentTempActionInfo currentInfo = new CurrentTempActionInfo(); public static CachedTempActionInfo cachedInfo = new CachedTempActionInfo(); - public Timer timer; + private Timer timer; public TempActions(DiscordSocketClient client) { @@ -27,22 +29,23 @@ public TempActions(DiscordSocketClient client) client.UserJoined += CheckNewUser; } - public async Task Ready() + private async Task Ready() { client.Ready -= Ready; timer = new Timer((_) => _ = ActCheckExec()); - timer.Change(0, 30000); + timer.Change(0, 45000); } - async Task CheckNewUser(SocketGuildUser user) + private async Task CheckNewUser(SocketGuildUser user) { ModerationSettings settings = user.Guild?.LoadFromFile(); TempActionList actions = user.Guild?.LoadFromFile(); - if (settings == null || user.Guild?.GetRole(settings.mutedRole) == null || (actions?.tempMutes?.IsNullOrEmpty() ?? true)) return; - if (actions.tempMutes.Any(tempMute => tempMute.user == user.Id)) _ = user.AddRoleAsync(user.Guild.GetRole(settings.mutedRole)); + //Can be done better and cleaner + if (settings == null || user.Guild?.GetRole(settings.mutedRole) == null || (actions?.tempMutes?.Count is null or 0)) return; + if (actions.tempMutes.Any(tempMute => tempMute.User == user.Id)) await user.AddRoleAsync(user.Guild.GetRole(settings.mutedRole)); } - public static async Task CheckTempActs(DiscordSocketClient client, bool debug = false) + public static async Task CheckTempActs(DiscordSocketClient client, bool debug = false, CancellationToken? ct = null) { RequestOptions requestOptions = RequestOptions.Default; requestOptions.RetryMode = RetryMode.AlwaysRetry; @@ -51,6 +54,7 @@ public static async Task CheckTempActs(DiscordSocketClient client, bool debug = currentInfo.checkedGuilds = 0; foreach (SocketGuild sockGuild in client.Guilds) { + ct?.ThrowIfCancellationRequested(); currentInfo.checkedMutes = 0; if (currentInfo.checkedGuilds > client.Guilds.Count) { @@ -68,36 +72,31 @@ public static async Task CheckTempActs(DiscordSocketClient client, bool debug = currentInfo.checkedGuilds++; if (actions != null) { - if (!actions.tempBans.IsNullOrEmpty()) + if (actions.tempBans?.Count is not null or 0) { - var bans = await sockGuild.GetBansAsync(requestOptions); currentInfo.editedBans = new List(actions.tempBans); foreach (TempAct tempBan in actions.tempBans) { try { - RestBan ban = bans.FirstOrDefault(tBan => tBan.User.Id == tempBan.user); - if (ban == null && bans != null) + RestBan ban = await sockGuild.GetBanAsync(tempBan.User, requestOptions); + if (ban == null) { //If manual unban - var user = await client.Rest.GetUserAsync(tempBan.user); + var user = await client.Rest.GetUserAsync(tempBan.User); currentInfo.editedBans.Remove(tempBan); user?.TryNotify($"As you might know, you have been manually unbanned in {sockGuild.Name} discord"); //_ = new LogMessage(LogSeverity.Warning, "TempAction", "Tempbanned person isn't banned").Log(); if (user == null) - { - DiscordLogging.LogManualEndTempAct(sockGuild, tempBan.user, "bann", tempBan.dateBanned); - } + DiscordLogging.LogManualEndTempAct(sockGuild, tempBan.User, "bann", tempBan.DateBanned); else - { - DiscordLogging.LogManualEndTempAct(sockGuild, user, "bann", tempBan.dateBanned); - } + DiscordLogging.LogManualEndTempAct(sockGuild, user, "bann", tempBan.DateBanned); } - else if (DateTime.UtcNow >= tempBan.dateBanned.Add(tempBan.length)) + else if (DateTime.UtcNow >= tempBan.DateBanned.Add(tempBan.Length)) { RestUser rUser = ban.User; - await restGuild.RemoveBanAsync(tempBan.user, requestOptions); + await sockGuild.RemoveBanAsync(tempBan.User, requestOptions); currentInfo.editedBans.Remove(tempBan); - DiscordLogging.LogEndTempAct(sockGuild, rUser, "bann", tempBan.reason, tempBan.length); + DiscordLogging.LogEndTempAct(sockGuild, rUser, "bann", tempBan.Reason, tempBan.Length); } } catch (Exception e) @@ -117,24 +116,24 @@ public static async Task CheckTempActs(DiscordSocketClient client, bool debug = } else if (debug) Console.Write($"no tempbans, "); ModerationSettings settings = sockGuild.LoadFromFile(); - if (settings != null && sockGuild.GetRole(settings.mutedRole) != null && actions.tempMutes.NotEmpty()) + if (settings is not null && sockGuild.GetRole(settings.mutedRole) != null && actions.tempMutes?.Count is not null or 0) { - RestRole mutedRole = restGuild.GetRole(settings.mutedRole); + var mutedRole = sockGuild.GetRole(settings.mutedRole); List editedMutes = new List(actions.tempMutes); foreach (TempAct tempMute in actions.tempMutes) { currentInfo.checkedMutes++; try { - IGuildUser gUser = await client.SuperGetUser(sockGuild, tempMute.user); + IGuildUser gUser = sockGuild.GetUser(tempMute.User) ?? await restGuild.SuperGetUser(tempMute.User); if (gUser != null && !gUser.RoleIds.Contains(settings.mutedRole)) { //User missing muted role, must have been manually unmuted _ = gUser.TryNotify($"As you might know, you have been manually unmuted in {sockGuild.Name} discord"); editedMutes.Remove(tempMute); - DiscordLogging.LogManualEndTempAct(sockGuild, gUser, "mut", tempMute.dateBanned); + DiscordLogging.LogManualEndTempAct(sockGuild, gUser, "mut", tempMute.DateBanned); _ = (!editedMutes.Contains(tempMute)).AssertAsync("Tempmute not removed?!"); } - else if (DateTime.UtcNow >= tempMute.dateBanned.Add(tempMute.length)) + else if (DateTime.UtcNow >= tempMute.DateBanned.Add(tempMute.Length)) { //Normal mute end if (gUser != null) { @@ -143,11 +142,11 @@ public static async Task CheckTempActs(DiscordSocketClient client, bool debug = if (gUser == null || !gUser.RoleIds.Contains(settings.mutedRole)) { //Doesn't remove tempmute if unmuting fails IUser user = gUser; //Gets user to try to message - user ??= await client.SuperGetUser(tempMute.user); + user ??= await client.SuperGetUser(tempMute.User); if (user != null) { // if possible to message, message and log - DiscordLogging.LogEndTempAct(sockGuild, user, "mut", tempMute.reason, tempMute.length); - _ = user.Notify($"untemp-muted", tempMute.reason, sockGuild); + DiscordLogging.LogEndTempAct(sockGuild, user, "mut", tempMute.Reason, tempMute.Length); + _ = user.Notify($"untemp-muted", tempMute.Reason, sockGuild); } editedMutes.Remove(tempMute); } @@ -180,7 +179,7 @@ public static async Task CheckTempActs(DiscordSocketClient client, bool debug = _ = (currentInfo.checkedGuilds > 0).AssertWarnAsync("Checked 0 guilds for tempacts?"); } - catch (Exception e) + catch (Exception e) when (e is not OperationCanceledException) { await new LogMessage(LogSeverity.Error, "TempAct", "Something went wrong checking temp actions", e).Log(); } @@ -190,13 +189,25 @@ public async Task ActCheckExec() { if (currentInfo.checking) { - await new LogMessage(LogSeverity.Critical, "TempAct", $"Temp actions took longer than 30 seconds to complete and still haven't canceled\nIt has gone through {currentInfo?.checkedGuilds}/{client.Guilds.Count} guilds").Log(); + await new LogMessage(LogSeverity.Critical, "TempAct", + $"Check took longer than 30 seconds to complete and still haven't canceled\nIt has gone through {currentInfo?.checkedGuilds}/{client.Guilds.Count} guilds").Log(); return; } currentInfo.checking = true; DateTime start = DateTime.UtcNow; - await CheckTempActs(client); + var timeoutPolicy = Policy.TimeoutAsync(40, Polly.Timeout.TimeoutStrategy.Optimistic, onTimeoutAsync: async (context, timespan, task) => { + await new LogMessage(LogSeverity.Critical, "TempAct", + $"TempAct check canceled at {DateTime.UtcNow.Subtract(start).Humanize(precision: 2)} and through {currentInfo?.checkedGuilds}/{client.Guilds.Count} guilds").Log(); + //Won't continue to below so have to do this? + ResetInfo(start); }); + await timeoutPolicy.ExecuteAsync(async ct => await CheckTempActs(client, ct: ct), CancellationToken.None, false); + //Won't continue if above times out? + ResetInfo(start); + } + + public void ResetInfo(DateTime start) + { TimeSpan execTime = DateTime.UtcNow.Subtract(start); cachedInfo.checkExecutionTimes.Enqueue(execTime); cachedInfo.lastCheck = DateTime.UtcNow; diff --git a/BotCatMaxy/TypeReaders/TimeSpanTypeReader.cs b/BotCatMaxy/TypeReaders/TimeSpanTypeReader.cs new file mode 100644 index 0000000..0d0aaf6 --- /dev/null +++ b/BotCatMaxy/TypeReaders/TimeSpanTypeReader.cs @@ -0,0 +1,59 @@ +using BotCatMaxy.TypeReaders; +using Discord.Commands; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Text.RegularExpressions; + +namespace BotCatMaxy.TypeReaders +{ + public class TimeSpanTypeReader : TypeReader + { + private const string regex = @"\d+\.?\d?[ywdhms]"; + + public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) + { + var result = ParseTime(input); + return (result != null) ? + Task.FromResult(TypeReaderResult.FromSuccess(result)) + : Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse TimeSpan")); + } + + public static TimeSpan? ParseTime(string input) + { + input = input.ToLowerInvariant(); + var results = Regex.Matches(input, regex, RegexOptions.CultureInvariant | RegexOptions.Compiled); + if (results.Count == 0 || results.Sum(m => m.Length) != input.Length) return null; + TimeSpan result = new TimeSpan(); + foreach (Match match in results) + { + string s = match.Value; + if (!double.TryParse(s.Remove(match.Length - 1), out double amount)) return null; + TimeSpan increment = s.Last() switch + { + 'y' => TimeSpan.FromDays(amount * 365.2425), + 'w' => TimeSpan.FromDays(amount * 7), + 'd' => TimeSpan.FromDays(amount), + 'h' => TimeSpan.FromHours(amount), + 'm' => TimeSpan.FromMinutes(amount), + 's' => TimeSpan.FromSeconds(amount), + _ => throw new NotImplementedException(), + }; + result = result.Add(increment); + } + return result; + } + } +} + +namespace BotCatMaxy +{ + public static class TimeSpanUtilities + { + public static TimeSpan? ToTime(this string s) + => TimeSpanTypeReader.ParseTime(s); + } +} diff --git a/BotCatMaxy/TypeReaders/UserRefTypeReader.cs b/BotCatMaxy/TypeReaders/UserRefTypeReader.cs index 58cd3ea..e1218b1 100644 --- a/BotCatMaxy/TypeReaders/UserRefTypeReader.cs +++ b/BotCatMaxy/TypeReaders/UserRefTypeReader.cs @@ -14,13 +14,8 @@ public class UserRefTypeReader : TypeReader { public override async Task ReadAsync(ICommandContext context, string input, IServiceProvider services) { - IReadOnlyCollection guildUsers = ImmutableArray.Create(); SocketGuildUser gUserResult = null; - SocketUser userResult = null; - ulong? IDResult = null; - - if (context.Guild != null) - guildUsers = await context.Guild.GetUsersAsync(CacheMode.CacheOnly).ConfigureAwait(false); + SocketUser userResult; //By Mention (1.0) if (MentionUtils.TryParseUser(input, out var id)) diff --git a/BotCatMaxy/Utilities/CollectionUtilities.cs b/BotCatMaxy/Utilities/CollectionUtilities.cs new file mode 100644 index 0000000..5041a72 --- /dev/null +++ b/BotCatMaxy/Utilities/CollectionUtilities.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BotCatMaxy +{ + //will contain enumerable utilities because close enough in purpose, maybe split later if too big + public static class CollectionUtilities + { + public static string ListItems(this IEnumerable list, string joiner = " ") + { + string items = null; + var count = (list as ICollection)?.Count ?? list?.Count(); + if (count is not null or 0) + { + (list as ICollection)?.RemoveNullEntries(); + foreach (string item in list) + { + if (items == null) items = ""; + else items += joiner; + items += item; + } + } + return items; + } + public static void RemoveNullEntries(this ICollection list) + { + if (list?.Count is not null or 0) + foreach (T thing in list) + if (thing == null) + list.Remove(thing); + } + } +} diff --git a/BotCatMaxy/Utilities.cs b/BotCatMaxy/Utilities/GeneralUtilities.cs similarity index 56% rename from BotCatMaxy/Utilities.cs rename to BotCatMaxy/Utilities/GeneralUtilities.cs index ea28332..14a030d 100644 --- a/BotCatMaxy/Utilities.cs +++ b/BotCatMaxy/Utilities/GeneralUtilities.cs @@ -8,18 +8,20 @@ using Humanizer; using MongoDB.Bson; using MongoDB.Driver; +using Polly; using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.Contracts; +using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; namespace BotCatMaxy { - public static class Utilities + public static class GeneralUtilities { public static IMongoCollection GetCollection(this IGuild guild, bool createDir = true) { @@ -38,88 +40,11 @@ public static IMongoCollection GetCollection(this IGuild guild, bo return null; } - public static bool HasAdmin(this SocketGuildUser user) - { - if (user == null) return false; - if (user.Guild.Owner.Id == user.Id) - { - return true; - } - - foreach (SocketRole role in (user).Roles) - { - if (role.Permissions.Administrator) - { - return true; - } - } - return false; - } - - public static bool CanWarn(this SocketGuildUser user) - { - if (HasAdmin(user)) - { - return true; - } - - foreach (SocketRole role in user.Roles) - { - if (role.Permissions.KickMembers) - { - return true; - } - } - ModerationSettings settings = user.Guild.LoadFromFile(); - if (settings != null && settings.ableToWarn != null && settings.ableToWarn.Count > 0) - { - if (user.RoleIDs().Intersect(settings.ableToWarn).Any()) - { - return true; - } - } - return false; - } - public static List RoleIDs(this SocketGuildUser user) { return user.Roles.Select(role => role.Id).ToList(); } - public static bool CantBeWarned(this SocketGuildUser user) - { - if (user == null) return false; - if (user.HasAdmin()) return true; - - ModerationSettings settings = user.Guild.LoadFromFile(); - if (settings != null) - { - List rolesUnableToBeWarned = new List(); - foreach (ulong roleID in settings.cantBeWarned) rolesUnableToBeWarned.Add(user.Guild.GetRole(roleID)); - if (user.Roles.Intersect(rolesUnableToBeWarned).Any()) return true; - } - return false; - } - - public static SocketGuild GetGuild(SocketGuildChannel channel) - { - return channel.Guild; - } - - public static void RemoveNullEntries(this ICollection list) - { - if (list != null || list.Count > 0) - { - foreach (T thing in list) - { - if (thing == null) - { - list.Remove(thing); - } - } - } - } - public static string Suffix(this int num) { if (num.ToString().EndsWith("11")) return num.ToString() + "th"; @@ -164,85 +89,12 @@ public static string NickOrUsername(this SocketGuildUser user) else return user.Nickname; } - public static TimeSpan? ToTime(this string s) - { - try - { - double amount = double.Parse(s.Remove(s.Length - 1)); - if (s.ToLower().EndsWith("w")) - { - return TimeSpan.FromDays(amount * 7); - } - else if (s.ToLower().EndsWith('d')) - { - return TimeSpan.FromDays(amount); - } - else if (s.ToLower().EndsWith('h')) - { - return TimeSpan.FromHours(amount); - } - else if (s.ToLower().EndsWith("m")) - { - return TimeSpan.FromMinutes(amount); - } - else if (s.ToLower().EndsWith("s")) - { - return TimeSpan.FromSeconds(amount); - } - else if (s.ToLower().EndsWith("y")) - { - return TimeSpan.FromDays(amount * 365.2425); - } - } - catch { } - return null; - } - - public static bool IsNullOrEmpty(this ICollection list) - { - if (list == null || list.Count == 0) return true; - else return false; - } - public static bool NotEmpty(this IEnumerable list, int needAmount = 0) - { - if (list == null || list.ToArray().Length <= needAmount) return false; - else return true; - } - public static string Pluralize(this string s, float num) { if (num == 1) return s; else return s.Pluralize(); } - public static async Task TryNotify(this IUser user, string message) - { - try - { - var sentMessage = await user?.SendMessageAsync(message); - if (sentMessage == null) return false; - return true; - } - catch - { - return false; - } - } - - public static async Task TryNotify(this IUser user, Embed embed) - { - try - { - var sentMessage = await user?.SendMessageAsync(embed: embed); - if (sentMessage == null) return false; - return true; - } - catch - { - return false; - } - } - public static void DeleteOrRespond(this SocketMessage message, string toSay, IGuild guild, LogSettings settings = null) { if (settings == null) settings = guild.LoadFromFile(false); @@ -255,29 +107,6 @@ public static void DeleteOrRespond(this SocketMessage message, string toSay, IGu } } - public static string ListItems(this IEnumerable list, string joiner = " ") - { - string items = null; - if (list.NotEmpty()) - { - (list as ICollection)?.RemoveNullEntries(); - foreach (string item in list) - { - if (items == null) items = ""; - else items += joiner; - items += item; - } - } - return items; - } - - public static bool CanActOn(this SocketGuildUser focus, SocketGuildUser comparer) - { - if (focus.Roles.Select(role => role.Position).Max() > comparer.Roles.Select(role => role.Position).Max()) - return true; - return false; - } - public static string LimitedHumanize(this TimeSpan timeSpan, int precision = 2) { return timeSpan.Humanize(precision, maxUnit: Humanizer.Localisation.TimeUnit.Day, minUnit: Humanizer.Localisation.TimeUnit.Second); @@ -308,13 +137,13 @@ public static string Name(this UserRef userRef, bool showIDWithUser = false, boo { if (userRef == null) return "``ERROR``"; string name = null; - if (userRef.gUser?.Nickname != null) + if (userRef.GuildUser?.Nickname != null) { - name = userRef.gUser.Nickname.StrippedOfPing(); + name = userRef.GuildUser.Nickname.StrippedOfPing(); if (showRealName) //done since people with nicknames might have an innapropriate name under the nickname - name += $" aka {userRef.gUser.Username.StrippedOfPing()}"; + name += $" aka {userRef.GuildUser.Username.StrippedOfPing()}"; } - if (name == null && userRef.user != null) name = userRef.user.Username.StrippedOfPing(); + if (name == null && userRef.User != null) name = userRef.User.Username.StrippedOfPing(); if (name != null) { if (showIDWithUser) name += $" ({userRef.ID})"; @@ -326,15 +155,15 @@ public static string Name(this UserRef userRef, bool showIDWithUser = false, boo public static string Mention(this UserRef userRef) { if (userRef == null) return "``ERROR``"; - if (userRef.user != null) return userRef.user.Mention; + if (userRef.User != null) return userRef.User.Mention; return $"User with ID:{userRef.ID}"; } public static EmbedBuilder WithAuthor(this EmbedBuilder embed, UserRef userRef) { Contract.Requires(embed != null); - if (userRef.user != null) embed.WithAuthor(userRef.user); - else embed.WithAuthor($"Unkown user with ID:{userRef.ID}"); + if (userRef.User != null) embed.WithAuthor(userRef.User); + else embed.WithAuthor($"Unknown user with ID:{userRef.ID}"); return embed; } @@ -344,10 +173,10 @@ public static void RecordAct(this ulong userID, IGuild guild, TempAct tempAct, s acts.Add(new ActRecord() { type = type, - length = tempAct.length, + length = tempAct.Length, logLink = loglink, - reason = tempAct.reason, - time = tempAct.dateBanned + reason = tempAct.Reason, + time = tempAct.DateBanned }); userID.SaveActRecord(guild, acts); } @@ -364,31 +193,22 @@ public static async Task SuperGetUser(this DiscordSocketClient client, ul return await func.SuperGet(); } + public static readonly int[] ignoredHTTPErrors = { 500, 503, 530 }; public static async Task SuperGet(this Func> action) { - for (int i = 0; i < 3; i++) - { - try - { - return await action(); - } - catch (HttpException e) - { //If error happens and either has failed 3 times or non 500, 503, or 530 (not logged in) error - if (i == 2 || (e.HttpCode != System.Net.HttpStatusCode.ServiceUnavailable && e.HttpCode != System.Net.HttpStatusCode.InternalServerError && (int)e.HttpCode != 530)) throw; - } - } - throw new Exception($"SuperGet<{typeof(T).Name}> ran out of tries without throwing proper exception?"); + var result = await Policy + .Handle(e => ignoredHTTPErrors.Contains((int)e.HttpCode)) + .RetryAsync(3) + .ExecuteAndCaptureAsync(action); + return result.FinalHandledResult; } - public static async Task SuperGetUser(this DiscordSocketClient client, SocketGuild guild, ulong ID) + public static async Task SuperGetUser(this RestGuild guild, ulong ID) { - IGuildUser user = guild.GetUser(ID); - if (user != null) return user; var requestOptions = new RequestOptions() { RetryMode = RetryMode.AlwaysRetry }; Func> func = async () => { - RestGuild restGuild = await client.Rest.GetGuildAsync(guild.Id, requestOptions); - return await restGuild.GetUserAsync(ID, requestOptions); + return await guild.GetUserAsync(ID, requestOptions); }; return await func.SuperGet(); /*for (int i = 0; i < 3; i++) { diff --git a/BotCatMaxy/Utilities/NotifyUtilities.cs b/BotCatMaxy/Utilities/NotifyUtilities.cs new file mode 100644 index 0000000..1d9823b --- /dev/null +++ b/BotCatMaxy/Utilities/NotifyUtilities.cs @@ -0,0 +1,40 @@ +using Discord; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BotCatMaxy +{ + public static class NotifyUtilities + { + public static async Task TryNotify(this IUser user, string message) + { + try + { + var sentMessage = await user?.SendMessageAsync(message); + if (sentMessage == null) return false; + return true; + } + catch + { + return false; + } + } + + public static async Task TryNotify(this IUser user, Embed embed) + { + try + { + var sentMessage = await user?.SendMessageAsync(embed: embed); + if (sentMessage == null) return false; + return true; + } + catch + { + return false; + } + } + } +} diff --git a/BotCatMaxy/Utilities/PermissionUtilities.cs b/BotCatMaxy/Utilities/PermissionUtilities.cs new file mode 100644 index 0000000..1d0dbea --- /dev/null +++ b/BotCatMaxy/Utilities/PermissionUtilities.cs @@ -0,0 +1,79 @@ +using BotCatMaxy.Data; +using BotCatMaxy.Models; +using Discord.WebSocket; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BotCatMaxy +{ + public static class PermissionUtilities + { + public static bool HasAdmin(this SocketGuildUser user) + { + if (user == null) return false; + if (user.Guild.Owner.Id == user.Id) + { + return true; + } + + foreach (SocketRole role in (user).Roles) + { + if (role.Permissions.Administrator) + { + return true; + } + } + return false; + } + + public static bool CanWarn(this SocketGuildUser user) + { + if (HasAdmin(user)) + { + return true; + } + + foreach (SocketRole role in user.Roles) + { + if (role.Permissions.KickMembers) + { + return true; + } + } + ModerationSettings settings = user.Guild.LoadFromFile(); + if (settings != null && settings.ableToWarn != null && settings.ableToWarn.Count > 0) + { + if (user.RoleIDs().Intersect(settings.ableToWarn).Any()) + { + return true; + } + } + return false; + } + + public static bool CantBeWarned(this SocketGuildUser user) + { + if (user == null) return false; + if (user.HasAdmin()) return true; + + ModerationSettings settings = user.Guild.LoadFromFile(); + if (settings != null) + { + List rolesUnableToBeWarned = new List(); + foreach (ulong roleID in settings.cantBeWarned) rolesUnableToBeWarned.Add(user.Guild.GetRole(roleID)); + if (user.Roles.Intersect(rolesUnableToBeWarned).Any()) return true; + } + return false; + } + + public static bool CanActOn(this SocketGuildUser focus, SocketGuildUser comparer) + { + if (focus.Roles.Select(role => role.Position).Max() > comparer.Roles.Select(role => role.Position).Max()) + return true; + return false; + } + } +}