diff --git a/Commands/Debug.cs b/Commands/Debug.cs index 02b9bbda..f7134abc 100644 --- a/Commands/Debug.cs +++ b/Commands/Debug.cs @@ -1,7 +1,4 @@ -using Serilog.Events; -using Serilog.Formatting; - -namespace Cliptok.Commands +namespace Cliptok.Commands { internal class Debug : BaseCommandModule { @@ -251,8 +248,8 @@ public async Task MostWarningsCmd(CommandContext ctx) if (ulong.TryParse(key.ToString(), out ulong number)) { var warnings = Program.db.HashGetAll(key); - Dictionary warningdict = new(); - foreach(var warning in warnings) + Dictionary warningdict = new(); + foreach (var warning in warnings) { var warnobject = JsonConvert.DeserializeObject(warning.Value); warningdict[(long)warning.Name] = warnobject; diff --git a/Commands/InteractionCommands/AnnouncementInteractions.cs b/Commands/InteractionCommands/AnnouncementInteractions.cs index c4a87a23..ac1c0257 100644 --- a/Commands/InteractionCommands/AnnouncementInteractions.cs +++ b/Commands/InteractionCommands/AnnouncementInteractions.cs @@ -1,6 +1,4 @@ -using System.Linq; - -namespace Cliptok.Commands.InteractionCommands +namespace Cliptok.Commands.InteractionCommands { internal class AnnouncementInteractions : ApplicationCommandModule { @@ -182,7 +180,7 @@ public async Task AnnounceBuildSlashCommand(InteractionContext ctx, { noPingMsgString += $"\n\nDiscuss it here: {threadChannel.Mention}"; } - else if (insiderChannel1 == "Canary" && insiderChannel2 == "" && Program.cfgjson.InsiderCanaryThread != 0 && autothreadName == "Build {0} ({1})" && !canaryCreateNewThread) + else if (insiderChannel1 == "Canary" && insiderChannel2 == "" && Program.cfgjson.InsiderCanaryThread != 0 && autothreadName == "Build {0} ({1})" && !canaryCreateNewThread) { threadChannel = await ctx.Client.GetChannelAsync(Program.cfgjson.InsiderCanaryThread); noPingMsgString += $"\n\nDiscuss it here: {threadChannel.Mention}"; diff --git a/Commands/InteractionCommands/ContextCommands.cs b/Commands/InteractionCommands/ContextCommands.cs index dfd3b5e0..19cb4de4 100644 --- a/Commands/InteractionCommands/ContextCommands.cs +++ b/Commands/InteractionCommands/ContextCommands.cs @@ -18,7 +18,7 @@ public async Task ContextAvatar(ContextMenuContext ctx) await ctx.RespondAsync(null, embed, ephemeral: true); } - + [ContextMenu(DiscordApplicationCommandType.UserContextMenu, "Show Notes", defaultPermission: false)] [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), SlashCommandPermissions(DiscordPermissions.ModerateMembers)] public async Task ShowNotes(ContextMenuContext ctx) diff --git a/Commands/InteractionCommands/DebugInteractions.cs b/Commands/InteractionCommands/DebugInteractions.cs index eb7c80d1..471f5a49 100644 --- a/Commands/InteractionCommands/DebugInteractions.cs +++ b/Commands/InteractionCommands/DebugInteractions.cs @@ -108,7 +108,7 @@ public async Task UserInfoSlashCommand(InteractionContext ctx, [Option("user", " { await ctx.RespondAsync(embed: await DiscordHelpers.GenerateUserEmbed(user, ctx.Guild), ephemeral: !publicMessage); } - + [SlashCommand("muteinfo", "Show information about the mute for a user.")] [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator)] [SlashCommandPermissions(DiscordPermissions.ModerateMembers)] @@ -119,7 +119,7 @@ public async Task MuteInfoSlashCommand( { await ctx.RespondAsync(embed: await MuteHelpers.MuteStatusEmbed(targetUser, ctx.Guild), ephemeral: !isPublic); } - + [SlashCommand("baninfo", "Show information about the ban for a user.")] [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator)] [SlashCommandPermissions(DiscordPermissions.ModerateMembers)] diff --git a/Commands/InteractionCommands/JoinwatchInteractions.cs b/Commands/InteractionCommands/JoinwatchInteractions.cs index 5b8d20f4..2bb23bfd 100644 --- a/Commands/InteractionCommands/JoinwatchInteractions.cs +++ b/Commands/InteractionCommands/JoinwatchInteractions.cs @@ -16,7 +16,7 @@ public async Task JoinwatchAdd(InteractionContext ctx, if (joinWatchlist.Contains(user.Id)) { // User is already watched - + // Get current note; if it's the same, do nothing var currentNote = await Program.db.HashGetAsync("joinWatchedUsersNotes", user.Id); if (currentNote == note || (string.IsNullOrWhiteSpace(currentNote) && string.IsNullOrWhiteSpace(note))) @@ -24,9 +24,9 @@ public async Task JoinwatchAdd(InteractionContext ctx, await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} {user.Mention} is already being watched with the same note! Nothing to do."); return; } - + // If note is different, update it - + // If new note is empty, remove instead of changing to empty string! if (string.IsNullOrWhiteSpace(note)) { @@ -36,7 +36,7 @@ public async Task JoinwatchAdd(InteractionContext ctx, else { await Program.db.HashSetAsync("joinWatchedUsersNotes", user.Id, note); - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully updated the note for {user.Mention}:\n> {note}"); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully updated the note for {user.Mention}:\n> {note}"); } } else @@ -55,14 +55,14 @@ public async Task JoinwatchRemove(InteractionContext ctx, [Option("user", "The user to stop watching for joins and leaves of.")] DiscordUser user) { var joinWatchlist = await Program.db.ListRangeAsync("joinWatchedUsers"); - + // Check user watch status first; error if not watched if (!joinWatchlist.Contains(user.Id)) { await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} {user.Mention} is not being watched! Nothing to do."); return; } - + Program.db.ListRemove("joinWatchedUsers", joinWatchlist.First(x => x == user.Id)); await Program.db.HashDeleteAsync("joinWatchedUsersNotes", user.Id); await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully unwatched {user.Mention}!"); @@ -73,11 +73,11 @@ public async Task JoinwatchStatus(InteractionContext ctx, [Option("user", "The user whose joinwatch status to check.")] DiscordUser user) { var joinWatchlist = await Program.db.ListRangeAsync("joinWatchedUsers"); - + if (joinWatchlist.Contains(user.Id)) { var note = await Program.db.HashGetAsync("joinWatchedUsersNotes", user.Id); - + if (string.IsNullOrWhiteSpace(note)) await ctx.RespondAsync($"{Program.cfgjson.Emoji.Information} {user.Mention} is currently being watched, but no note is set."); else diff --git a/Commands/InteractionCommands/MuteInteractions.cs b/Commands/InteractionCommands/MuteInteractions.cs index 073d7990..c8c167cc 100644 --- a/Commands/InteractionCommands/MuteInteractions.cs +++ b/Commands/InteractionCommands/MuteInteractions.cs @@ -98,7 +98,7 @@ public async Task TqsMuteSlashCommand( [Option("reason", "The reason for the mute.")] string reason) { await ctx.DeferAsync(ephemeral: true); - + // Only allow usage in #tech-support, #tech-support-forum, and their threads if (ctx.Channel.Id != Program.cfgjson.TechSupportChannel && ctx.Channel.Id != Program.cfgjson.SupportForumId && @@ -108,18 +108,18 @@ public async Task TqsMuteSlashCommand( await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"{Program.cfgjson.Emoji.Error} This command can only be used in <#{Program.cfgjson.TechSupportChannel}>, <#{Program.cfgjson.SupportForumId}>, and threads in those channels!")); return; } - + // Check if the user is already muted; disallow TQS-mute if so - + DiscordRole mutedRole = ctx.Guild.GetRole(Program.cfgjson.MutedRole); DiscordRole tqsMutedRole = ctx.Guild.GetRole(Program.cfgjson.TqsMutedRole); - + if (await Program.db.HashExistsAsync("mutes", targetUser.Id) || ctx.Member.Roles.Contains(mutedRole) || ctx.Member.Roles.Contains(tqsMutedRole)) { await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"{Program.cfgjson.Emoji.Error} {ctx.User.Mention}, that user is already muted.")); return; } - + // Get member DiscordMember targetMember = default; try diff --git a/Commands/InteractionCommands/SecurityActionInteractions.cs b/Commands/InteractionCommands/SecurityActionInteractions.cs index 3cfcf598..93fc18d7 100644 --- a/Commands/InteractionCommands/SecurityActionInteractions.cs +++ b/Commands/InteractionCommands/SecurityActionInteractions.cs @@ -21,7 +21,7 @@ public async Task SlashPauseDMs(InteractionContext ctx, [Option("time", "The amo return; } var dmsDisabledUntil = t.ToUniversalTime().ToString("o"); - + // get current security actions to avoid unintentionally resetting invites_disabled_until var currentActions = await SecurityActionHelpers.GetCurrentSecurityActions(ctx.Guild.Id); JToken invitesDisabledUntil; @@ -29,7 +29,7 @@ public async Task SlashPauseDMs(InteractionContext ctx, [Option("time", "The amo invitesDisabledUntil = null; else invitesDisabledUntil = currentActions["invites_disabled_until"]; - + // create json body var newSecurityActions = JsonConvert.SerializeObject(new { @@ -55,7 +55,7 @@ public async Task SlashPauseDMs(InteractionContext ctx, [Option("time", "The amo public async Task SlashUnpauseDMs(InteractionContext ctx) { // need to make our own api calls because D#+ can't do this natively? - + // get current security actions to avoid unintentionally resetting invites_disabled_until var currentActions = await SecurityActionHelpers.GetCurrentSecurityActions(ctx.Guild.Id); JToken dmsDisabledUntil, invitesDisabledUntil; @@ -76,14 +76,14 @@ public async Task SlashUnpauseDMs(InteractionContext ctx) await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} DMs are already unpaused!"); return; } - + // create json body var newSecurityActions = JsonConvert.SerializeObject(new { invites_disabled_until = invitesDisabledUntil, dms_disabled_until = (object)null, }); - + // set actions var setActionsResponse = await SecurityActionHelpers.SetCurrentSecurityActions(ctx.Guild.Id, newSecurityActions); diff --git a/Commands/InteractionCommands/UserNoteInteractions.cs b/Commands/InteractionCommands/UserNoteInteractions.cs index 546fb560..1453dbd9 100644 --- a/Commands/InteractionCommands/UserNoteInteractions.cs +++ b/Commands/InteractionCommands/UserNoteInteractions.cs @@ -18,7 +18,7 @@ public async Task AddUserNoteAsync(InteractionContext ctx, [Option("show_once", "Whether to show this note once and then discard it. Default: false")] bool showOnce = false) { await ctx.CreateResponseAsync(DiscordInteractionResponseType.DeferredChannelMessageWithSource, new DiscordInteractionResponseBuilder().AsEphemeral()); - + // Assemble new note long noteId = Program.db.StringIncrement("totalWarnings"); UserNote note = new() @@ -34,13 +34,13 @@ public async Task AddUserNoteAsync(InteractionContext ctx, Timestamp = DateTime.Now, Type = WarningType.Note }; - + await Program.db.HashSetAsync(user.Id.ToString(), note.NoteId, JsonConvert.SerializeObject(note)); - + // Log to mod-logs var embed = await GenerateUserNoteDetailEmbedAsync(note, user); await LogChannelHelper.LogMessageAsync("mod", $"{Program.cfgjson.Emoji.Information} New note for {user.Mention}!", embed); - + // Respond await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Success} Successfully added note!").AsEphemeral()); } @@ -48,7 +48,7 @@ public async Task AddUserNoteAsync(InteractionContext ctx, [SlashCommand("delete", "Delete a note.")] public async Task RemoveUserNoteAsync(InteractionContext ctx, [Option("user", "The user whose note to delete.")] DiscordUser user, - [Autocomplete(typeof(NotesAutocompleteProvider))] [Option("note", "The note to delete.")] string targetNote) + [Autocomplete(typeof(NotesAutocompleteProvider))][Option("note", "The note to delete.")] string targetNote) { // Get note UserNote note; @@ -61,29 +61,29 @@ public async Task RemoveUserNoteAsync(InteractionContext ctx, await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} I couldn't find that note! Make sure you've got the right ID.").AsEphemeral()); return; } - + // If user manually provided an ID of a warning, refuse the request and suggest /delwarn instead if (note.Type == WarningType.Warning) { await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} That's a warning, not a note! Try using `/delwarn` instead, or make sure you've got the right note ID.").AsEphemeral()); return; } - + // Delete note await Program.db.HashDeleteAsync(user.Id.ToString(), note.NoteId); - + // Log to mod-logs var embed = new DiscordEmbedBuilder(await GenerateUserNoteDetailEmbedAsync(note, user)).WithColor(0xf03916); await LogChannelHelper.LogMessageAsync("mod", $"{Program.cfgjson.Emoji.Deleted} Note deleted: `{note.NoteId}` (belonging to {user.Mention}, deleted by {ctx.User.Mention})", embed); - + // Respond await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Success} Successfully deleted note!").AsEphemeral()); } - + [SlashCommand("edit", "Edit a note for a user.")] public async Task EditUserNoteAsync(InteractionContext ctx, [Option("user", "The user to edit a note for.")] DiscordUser user, - [Autocomplete(typeof(NotesAutocompleteProvider))] [Option("note", "The note to edit.")] string targetNote, + [Autocomplete(typeof(NotesAutocompleteProvider))][Option("note", "The note to edit.")] string targetNote, [Option("new_text", "The new note text. Leave empty to not change.")] string newNoteText = default, [Option("show_on_modmail", "Whether to show the note when the user opens a modmail thread.")] bool? showOnModmail = null, [Option("show_on_warn", "Whether to show the note when the user is warned.")] bool? showOnWarn = null, @@ -101,25 +101,25 @@ public async Task EditUserNoteAsync(InteractionContext ctx, await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} I couldn't find that note! Make sure you've got the right ID.").AsEphemeral()); return; } - + // If new text is not provided, use old text if (newNoteText == default) newNoteText = note.NoteText; - + // If no changes are made, refuse the request if (note.NoteText == newNoteText && showOnModmail is null && showOnWarn is null && showAllMods is null && showOnce is null) { await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} You didn't change anything about the note!").AsEphemeral()); return; } - + // If user manually provided an ID of a warning, refuse the request and suggest /editwarn instead if (note.Type == WarningType.Warning) { await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} That's a warning, not a note! Try using `/editwarn` instead, or make sure you've got the right note ID.").AsEphemeral()); return; } - + // For any options the user didn't provide, use options from the note if (showOnModmail is null) showOnModmail = note.ShowOnModmail; @@ -129,7 +129,7 @@ public async Task EditUserNoteAsync(InteractionContext ctx, showAllMods = note.ShowAllMods; if (showOnce is null) showOnce = note.ShowOnce; - + // Assemble new note note.ModUserId = ctx.User.Id; note.NoteText = newNoteText; @@ -138,13 +138,13 @@ public async Task EditUserNoteAsync(InteractionContext ctx, note.ShowAllMods = (bool)showAllMods; note.ShowOnce = (bool)showOnce; note.Type = WarningType.Note; - + await Program.db.HashSetAsync(user.Id.ToString(), note.NoteId, JsonConvert.SerializeObject(note)); - + // Log to mod-logs var embed = await GenerateUserNoteDetailEmbedAsync(note, user); await LogChannelHelper.LogMessageAsync("mod", $"{Program.cfgjson.Emoji.Information} Note edited: `{note.NoteId}` (belonging to {user.Mention})", embed); - + // Respond await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Success} Successfully edited note!").AsEphemeral()); } @@ -156,11 +156,11 @@ public async Task ListUserNotesAsync(InteractionContext ctx, { await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().AddEmbed(await GenerateUserNotesEmbedAsync(user)).AsEphemeral(!showPublicly)); } - + [SlashCommand("details", "Show the details of a specific note for a user.")] public async Task ShowUserNoteAsync(InteractionContext ctx, [Option("user", "The user whose note to show details for.")] DiscordUser user, - [Autocomplete(typeof(NotesAutocompleteProvider))] [Option("note", "The note to show.")] string targetNote, + [Autocomplete(typeof(NotesAutocompleteProvider))][Option("note", "The note to show.")] string targetNote, [Option("public", "Whether to show the note in public chat. Default: false")] bool showPublicly = false) { // Get note @@ -174,14 +174,14 @@ public async Task ShowUserNoteAsync(InteractionContext ctx, await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} I couldn't find that note! Make sure you've got the right ID.").AsEphemeral()); return; } - + // If user manually provided an ID of a warning, refuse the request and suggest /warndetails instead if (note.Type == WarningType.Warning) { await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} That's a warning, not a note! Try using `/warndetails` instead, or make sure you've got the right note ID.").AsEphemeral()); return; } - + // Respond await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().AddEmbed(await GenerateUserNoteDetailEmbedAsync(note, user)).AsEphemeral(!showPublicly)); } @@ -199,20 +199,20 @@ public async Task> Provider(AutocompleteC } var user = await ctx.Client.GetUserAsync((ulong)useroption.Value); - + var notes = Program.db.HashGetAll(user.Id.ToString()) .Where(x => JsonConvert.DeserializeObject(x.Value).Type == WarningType.Note).ToDictionary( x => x.Name.ToString(), x => JsonConvert.DeserializeObject(x.Value) ).OrderByDescending(x => x.Value.NoteId); - + foreach (var note in notes) { if (list.Count >= 25) break; - + string noteString = $"{StringHelpers.Pad(note.Value.NoteId)} - {StringHelpers.Truncate(note.Value.NoteText, 29, true)} - {TimeHelpers.TimeToPrettyFormat(DateTime.Now - note.Value.Timestamp, true)}"; - + if (ctx.FocusedOption.Value.ToString() == "" || note.Value.NoteText.Contains((string)ctx.FocusedOption.Value) || noteString.ToLower().Contains(ctx.FocusedOption.Value.ToString().ToLower())) list.Add(new DiscordAutoCompleteChoice(noteString, StringHelpers.Pad(note.Value.NoteId))); } diff --git a/Commands/InteractionCommands/WarningInteractions.cs b/Commands/InteractionCommands/WarningInteractions.cs index 71bcaf87..f42a1a0e 100644 --- a/Commands/InteractionCommands/WarningInteractions.cs +++ b/Commands/InteractionCommands/WarningInteractions.cs @@ -225,13 +225,13 @@ public async Task DelwarnSlashCommand(InteractionContext ctx, } UserWarning warning = GetWarning(targetUser.Id, warnId); - + if (warning.Type == WarningType.Note) { await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} That's a note, not a warning! Try using `/note remove` instead, or make sure you've got the right warning ID.", ephemeral: true); return; } - + if (warning is null) await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} I couldn't find a warning for that user with that ID! Please check again."); else if (GetPermLevel(ctx.Member) == ServerPermLevel.TrialModerator && warning.ModUserId != ctx.User.Id && warning.ModUserId != ctx.Client.CurrentUser.Id) @@ -291,13 +291,13 @@ public async Task EditWarnSlashCommand(InteractionContext ctx, } var warningObject = GetWarning(user.Id, warnId); - + if (warningObject.Type == WarningType.Note) { await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} That's a note, not a warning! Try using `/note edit` instead, or make sure you've got the right warning ID.", ephemeral: true); return; } - + if (warningObject is null) await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} I couldn't find a warning for that user with that ID! Please check again."); else if (GetPermLevel(ctx.Member) == ServerPermLevel.TrialModerator && warningObject.ModUserId != ctx.User.Id && warningObject.ModUserId != ctx.Client.CurrentUser.Id) diff --git a/Commands/Kick.cs b/Commands/Kick.cs index 17b490b4..2f969519 100644 --- a/Commands/Kick.cs +++ b/Commands/Kick.cs @@ -118,7 +118,8 @@ await LogChannelHelper.LogMessageAsync("mod", .WithAllowedMentions(Mentions.None) ); return true; - } catch + } + catch { return false; } diff --git a/Commands/Lists.cs b/Commands/Lists.cs index ec2d721e..519e8373 100644 --- a/Commands/Lists.cs +++ b/Commands/Lists.cs @@ -193,7 +193,7 @@ public async Task JoinWatch( if (note != "") { // User is already joinwatched, just update note - + // Get current note; if it's the same, do nothing var currentNote = await Program.db.HashGetAsync("joinWatchedUsersNotes", user.Id); if (currentNote == note || (string.IsNullOrWhiteSpace(currentNote) && string.IsNullOrWhiteSpace(note))) @@ -201,7 +201,7 @@ public async Task JoinWatch( await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} {user.Mention} is already being watched with the same note! Nothing to do."); return; } - + // If note is different, update it await Program.db.HashSetAsync("joinWatchedUsersNotes", user.Id, note); await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully updated the note for {user.Mention} (run again with no note to unwatch):\n> {note}"); diff --git a/Commands/Mutes.cs b/Commands/Mutes.cs index 0a2670d3..c9d58e2a 100644 --- a/Commands/Mutes.cs +++ b/Commands/Mutes.cs @@ -112,18 +112,18 @@ public async Task TqsMuteCmd( await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command can only be used in <#{Program.cfgjson.TechSupportChannel}>, <#{Program.cfgjson.SupportForumId}>, and threads in those channels!"); return; } - + // Check if the user is already muted; disallow TQS-mute if so - + DiscordRole mutedRole = ctx.Guild.GetRole(Program.cfgjson.MutedRole); DiscordRole tqsMutedRole = ctx.Guild.GetRole(Program.cfgjson.TqsMutedRole); - + if ((await Program.db.HashExistsAsync("mutes", targetUser.Id)) || (ctx.Member != default && (ctx.Member.Roles.Contains(mutedRole) || ctx.Member.Roles.Contains(tqsMutedRole)))) { await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} {ctx.User.Mention}, that user is already muted."); return; } - + // Get member DiscordMember targetMember = default; try diff --git a/Commands/SecurityActions.cs b/Commands/SecurityActions.cs index 98cdf9ef..b1c7851a 100644 --- a/Commands/SecurityActions.cs +++ b/Commands/SecurityActions.cs @@ -12,7 +12,7 @@ public async Task PauseDMs(CommandContext ctx, [Description("The amount of time await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} You must provide an amount of time to pause DMs for!"); return; } - + // need to make our own api calls because D#+ can't do this natively? // parse time from message @@ -37,7 +37,7 @@ public async Task PauseDMs(CommandContext ctx, [Description("The amount of time return; } var dmsDisabledUntil = t.ToUniversalTime().ToString("o"); - + // get current security actions to avoid unintentionally resetting invites_disabled_until var currentActions = await SecurityActionHelpers.GetCurrentSecurityActions(ctx.Guild.Id); JToken invitesDisabledUntil; @@ -45,7 +45,7 @@ public async Task PauseDMs(CommandContext ctx, [Description("The amount of time invitesDisabledUntil = null; else invitesDisabledUntil = currentActions["invites_disabled_until"]; - + // create json body var newSecurityActions = JsonConvert.SerializeObject(new { @@ -72,7 +72,7 @@ public async Task PauseDMs(CommandContext ctx, [Description("The amount of time public async Task UnpauseDMs(CommandContext ctx) { // need to make our own api calls because D#+ can't do this natively? - + // get current security actions to avoid unintentionally resetting invites_disabled_until var currentActions = await SecurityActionHelpers.GetCurrentSecurityActions(ctx.Guild.Id); JToken dmsDisabledUntil, invitesDisabledUntil; @@ -93,14 +93,14 @@ public async Task UnpauseDMs(CommandContext ctx) await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} DMs are already unpaused!"); return; } - + // create json body var newSecurityActions = JsonConvert.SerializeObject(new { invites_disabled_until = invitesDisabledUntil, dms_disabled_until = (object)null, }); - + // set actions var setActionsResponse = await SecurityActionHelpers.SetCurrentSecurityActions(ctx.Guild.Id, newSecurityActions); diff --git a/Commands/UserRoles.cs b/Commands/UserRoles.cs index 29fa1085..9faf2132 100644 --- a/Commands/UserRoles.cs +++ b/Commands/UserRoles.cs @@ -19,7 +19,7 @@ public static async Task GiveUserRolesAsync(CommandContext ctx, Func JsonConvert.DeserializeObject(x.Value).Type == WarningType.Note).ToDictionary( x => x.Name.ToString(), x => JsonConvert.DeserializeObject(x.Value) ); - + // Filter to notes set to notify on modmail var notesToNotify = notes.Where(x => x.Value.ShowOnModmail).ToDictionary(x => x.Key, x => x.Value); - + // If there are notes, build embed and add to message if (notesToNotify.Count != 0) { memberWarnInfo.AddEmbed(await UserNoteHelpers.GenerateUserNotesEmbedAsync(modmailMember, notesToUse: notesToNotify)); - + // For any notes set to show once, show the full note content in its own embed because it will not be able to be fetched manually foreach (var note in notesToNotify) if (memberWarnInfo.Embeds.Count < 10) // Limit to 10 embeds; this probably won't be an issue because we probably won't have that many 'show once' notes if (note.Value.ShowOnce) memberWarnInfo.AddEmbed(await UserNoteHelpers.GenerateUserNoteSimpleEmbedAsync(note.Value, modmailMember)); } - + // If message was built (if user is muted OR if user has notes to show on modmail), send it if (memberWarnInfo.Embeds.Count != 0) await message.Channel.SendMessageAsync(memberWarnInfo); - + // If any notes were shown & set to show only once, delete them now foreach (var note in notesToNotify.Where(note => note.Value.ShowOnce)) { // Delete note await Program.db.HashDeleteAsync(modmailMember.Id.ToString(), note.Key); - + // Log deletion to mod-logs channel var embed = new DiscordEmbedBuilder(await UserNoteHelpers.GenerateUserNoteDetailEmbedAsync(note.Value, modmailMember)).WithColor(0xf03916); await LogChannelHelper.LogMessageAsync("mod", $"{Program.cfgjson.Emoji.Deleted} Note `{note.Value.NoteId}` was automatically deleted after modmail thread creation (belonging to {modmailMember.Mention})", embed); @@ -234,7 +234,7 @@ public static async Task MessageHandlerAsync(DiscordClient client, DiscordMessag continue; } else - { + { (bool success, string flaggedWord) = Checks.ListChecks.CheckForNaughtyWords(message.Content.ToLower(), listItem); if (success) { diff --git a/Helpers/BanHelpers.cs b/Helpers/BanHelpers.cs index 5e007720..b0c9d057 100644 --- a/Helpers/BanHelpers.cs +++ b/Helpers/BanHelpers.cs @@ -173,7 +173,7 @@ await LykosAvatarMethods.UserOrMemberAvatarURL(user, Program.homeGuild, "png") if (await Program.db.HashExistsAsync("bans", user.Id)) { MemberPunishment ban = JsonConvert.DeserializeObject(Program.db.HashGet("bans", user.Id)); - + embedBuilder.WithDescription("User is banned.") .AddField("Banned", ban.ActionTime is null ? "Unknown time (Ban is too old)" : $"", true) .WithColor(new DiscordColor(0xFEC13D)); diff --git a/Helpers/DiscordHelpers.cs b/Helpers/DiscordHelpers.cs index 1e4b068b..a52ff5b5 100644 --- a/Helpers/DiscordHelpers.cs +++ b/Helpers/DiscordHelpers.cs @@ -53,7 +53,8 @@ public static int GetHier(DiscordMember target) { message = await channel.GetMessageAsync(messageReference.MessageId); return message; - } catch + } + catch { return null; } diff --git a/Helpers/MuteHelpers.cs b/Helpers/MuteHelpers.cs index f3f34d87..0ae4b4a7 100644 --- a/Helpers/MuteHelpers.cs +++ b/Helpers/MuteHelpers.cs @@ -135,7 +135,7 @@ public static (int MuteHours, int WarnsSinceThreshold) GetHoursToMuteFor(Diction { string fullReason = $"[{(isTqsMute ? "TQS " : "")}Mute by {DiscordHelpers.UniqueUsername(moderator)}]: {reason}"; await naughtyMember.GrantRoleAsync(mutedRole, fullReason); - + // for global mutes, issue timeout & kick from any voice channel; does not apply to TQS mutes as they are not server-wide if (!isTqsMute) { @@ -185,7 +185,7 @@ public static (int MuteHours, int WarnsSinceThreshold) GetHoursToMuteFor(Diction $"\nMute expires: ") .WithAllowedMentions(Mentions.None) ); - + await LogChannelHelper.LogMessageAsync("mod", new DiscordMessageBuilder() .WithContent($"{Program.cfgjson.Emoji.Muted} {naughtyUser.Mention} was TQS-muted for **{TimeHelpers.TimeToPrettyFormat(muteDuration, false)}** by {moderator.Mention}." + $"\nReason: **{reason}**" + @@ -359,7 +359,7 @@ await LogChannelHelper.LogMessageAsync("mod", wasTqsMute = true; // only true if TQS mute role was found & removed } } - + foreach (var role in member.Roles) { if (role.Name == "Muted" && role.Id != Program.cfgjson.MutedRole) @@ -403,7 +403,7 @@ await LogChannelHelper.LogMessageAsync("mod", string unmuteMsg = manual ? $"{Program.cfgjson.Emoji.Information} {targetUser.Mention} was successfully unmuted by {modUser.Mention}!" : $"{Program.cfgjson.Emoji.Information} Successfully unmuted {targetUser.Mention}!"; - + await LogChannelHelper.LogMessageAsync("mod", new DiscordMessageBuilder().WithContent(unmuteMsg).WithAllowedMentions(Mentions.None)); if (manual && muteDetailsJson.HasValue) diff --git a/Helpers/SecurityActionHelpers.cs b/Helpers/SecurityActionHelpers.cs index 7734e379..f69f4969 100644 --- a/Helpers/SecurityActionHelpers.cs +++ b/Helpers/SecurityActionHelpers.cs @@ -6,7 +6,7 @@ public static async Task GetCurrentSecurityActions(ulong guildId) { using HttpRequestMessage getActionsRequest = new(HttpMethod.Get, $"https://discord.com/api/v{Program.discord.GatewayVersion}/guilds/{guildId}"); getActionsRequest.Headers.Authorization = new AuthenticationHeaderValue("Bot", Environment.GetEnvironmentVariable("CLIPTOK_TOKEN") ?? Program.cfgjson.Core.Token); - + var getActionsResponse = await Program.httpClient.SendAsync(getActionsRequest); return ((JObject)JsonConvert.DeserializeObject(await getActionsResponse.Content.ReadAsStringAsync()))["incidents_data"]; } @@ -14,10 +14,10 @@ public static async Task GetCurrentSecurityActions(ulong guildId) public static async Task SetCurrentSecurityActions(ulong guildId, string newSecurityActions) { // create & send request - + using HttpRequestMessage setActionsRequest = new(HttpMethod.Put, $"https://discord.com/api/v{Program.discord.GatewayVersion}/guilds/{guildId}/incident-actions"); setActionsRequest.Headers.Authorization = new AuthenticationHeaderValue("Bot", Environment.GetEnvironmentVariable("CLIPTOK_TOKEN") ?? Program.cfgjson.Core.Token); - + setActionsRequest.Content = new StringContent(newSecurityActions, Encoding.UTF8, "application/json"); return await Program.httpClient.SendAsync(setActionsRequest); } diff --git a/Helpers/UserNoteHelpers.cs b/Helpers/UserNoteHelpers.cs index 6cf219de..2f939d6a 100644 --- a/Helpers/UserNoteHelpers.cs +++ b/Helpers/UserNoteHelpers.cs @@ -2,10 +2,10 @@ namespace Cliptok.Helpers { public class UserNoteHelpers { - public static async Task GenerateUserNotesEmbedAsync(DiscordUser user, bool showOnlyWarningNotes = false, Dictionary notesToUse = default) + public static async Task GenerateUserNotesEmbedAsync(DiscordUser user, bool showOnlyWarningNotes = false, Dictionary notesToUse = default) { Dictionary notes; - + // If provided with a set of notes, use them instead if (notesToUse == default) { @@ -14,7 +14,7 @@ public static async Task GenerateUserNotesEmbedAsync(DiscordUser u x => x.Name.ToString(), x => JsonConvert.DeserializeObject(x.Value) ); - + // Filter to 'show on warn' notes if requested if (showOnlyWarningNotes) notes = notes.Where(x => x.Value.ShowOnWarn).ToDictionary(x => x.Key, x => x.Value); @@ -23,14 +23,14 @@ public static async Task GenerateUserNotesEmbedAsync(DiscordUser u { notes = notesToUse; } - + // If there is only one note in the set to show, just show its details if (notes.Count == 1) { var noteDetailsEmbed = await GenerateUserNoteDetailEmbedAsync(notes.First().Value, user); return new DiscordEmbedBuilder(noteDetailsEmbed).WithFooter($"{noteDetailsEmbed.Footer.Text}\nThis is the user's only note, so it is shown in detail."); } - + var keys = notes.Keys.OrderByDescending(note => Convert.ToInt64(note)); string str = ""; @@ -56,16 +56,16 @@ await LykosAvatarMethods.UserOrMemberAvatarURL(user, Program.homeGuild, "png") foreach (string key in keys) { UserNote note = notes[key]; - + var text = note.NoteText; - + text = text.Replace("`", "\\`").Replace("*", "\\*"); if (text.Length > 29) { text = StringHelpers.Truncate(text, 29) + "…"; } - + str += $"`{StringHelpers.Pad(note.NoteId)}` **{text}** • \n"; } @@ -96,10 +96,10 @@ await LykosAvatarMethods.UserOrMemberAvatarURL(user, Program.homeGuild, "png") if (note.ShowOnce) embed.AddField("Showing Once Only", "This note was set to show only once. It has now been deleted!"); - + return embed; } - + public static async Task GenerateUserNoteDetailEmbedAsync(UserNote note, DiscordUser user) { DiscordEmbedBuilder embed = new DiscordEmbedBuilder() diff --git a/Helpers/WarningHelpers.cs b/Helpers/WarningHelpers.cs index 3e37f52c..07f65cb6 100644 --- a/Helpers/WarningHelpers.cs +++ b/Helpers/WarningHelpers.cs @@ -222,7 +222,7 @@ public static async Task GiveWarningAsync(DiscordUser targetUser, D }; Program.db.HashSet(targetUser.Id.ToString(), warning.WarningId, JsonConvert.SerializeObject(warning)); - + // If warning is automatic (if responsible moderator is a bot), add to list so the context message can be more-easily deleted later if (modUser.IsBot) Program.db.HashSet("automaticWarnings", warningId, JsonConvert.SerializeObject(warning)); @@ -267,7 +267,7 @@ public static async Task GiveWarningAsync(DiscordUser targetUser, D { await MuteHelpers.MuteUserAsync(targetUser, $"Automatic permanent mute after {warnsSinceThreshold} warnings in the past {acceptedThreshold} {thresholdSpan}.", modUser.Id, guild, channel); } - + // If warning was not automatic (not issued by a bot) and target user has notes to be shown on warn, alert the responsible moderator if (!modUser.IsBot) @@ -278,34 +278,34 @@ public static async Task GiveWarningAsync(DiscordUser targetUser, D x => x.Name.ToString(), x => JsonConvert.DeserializeObject(x.Value) ); - + // Get notes set to notify on warn var notesToNotifyFor = notes.Where(x => x.Value.ShowOnWarn).ToDictionary(x => x.Key, x => x.Value); - + // Get relevant notes ('show all mods' is true, or mod is responsible for note & warning) notesToNotifyFor = notesToNotifyFor.Where(x => x.Value.ShowAllMods || x.Value.ModUserId == modUser.Id).ToDictionary(x => x.Key, x => x.Value); - + // Alert moderator if there are relevant notes if (notesToNotifyFor.Count != 0) { var alertChannel = await Program.discord.GetChannelAsync(Program.cfgjson.InvestigationsChannelId); var msg = new DiscordMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Muted} {modUser.Mention}, {targetUser.Mention} has notes set to show when they are issued a warning!").AddEmbed(await UserNoteHelpers.GenerateUserNotesEmbedAsync(targetUser, true, notesToNotifyFor)).WithAllowedMentions(Mentions.All); - + // For any notes set to show once, show the full note content in its own embed because it will not be able to be fetched manually foreach (var note in notesToNotifyFor) if (msg.Embeds.Count < 10) // Limit to 10 embeds; this probably won't be an issue because we probably won't have that many 'show once' notes if (note.Value.ShowOnce) msg.AddEmbed(await UserNoteHelpers.GenerateUserNoteSimpleEmbedAsync(note.Value, targetUser)); - + await alertChannel.SendMessageAsync(msg); } - + // If any notes were shown & set to show only once, delete them now foreach (var note in notesToNotifyFor.Where(note => note.Value.ShowOnce)) { // Delete note await Program.db.HashDeleteAsync(targetUser.Id.ToString(), note.Key); - + // Log deletion to mod-logs channel var embed = new DiscordEmbedBuilder(await UserNoteHelpers.GenerateUserNoteDetailEmbedAsync(note.Value, targetUser)).WithColor(0xf03916); await LogChannelHelper.LogMessageAsync("mod", $"{Program.cfgjson.Emoji.Deleted} Note `{note.Value.NoteId}` was automatically deleted after a warning (belonging to {targetUser.Mention})", embed); diff --git a/Program.cs b/Program.cs index 73a10b48..6415f88b 100644 --- a/Program.cs +++ b/Program.cs @@ -1,8 +1,3 @@ -using DSharpPlus.Extensions; -using DSharpPlus.Net; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Serilog.Configuration; using System.Reflection; namespace Cliptok @@ -133,7 +128,7 @@ static async Task Main(string[] _) // Migration away from a broken attempt at a key in the past. db.KeyDelete("messages"); - + DiscordClientBuilder discordBuilder = DiscordClientBuilder.CreateDefault(token, DiscordIntents.All); discordBuilder.ConfigureLogging(logging => diff --git a/Structs.cs b/Structs.cs index a1547961..996ed021 100644 --- a/Structs.cs +++ b/Structs.cs @@ -304,7 +304,7 @@ public class ConfigJson } - public enum Level { Information, Warning, Error, Debug, Verbose } + public enum Level { Information, Warning, Error, Debug, Verbose } public class LogChannelConfig { @@ -501,7 +501,7 @@ public class UserRoleConfig [JsonProperty("giveaways")] public ulong Giveaways { get; private set; } - [JsonProperty("insider10beta")] + [JsonProperty("insider10beta")] public ulong Insider10Beta { get; private set; } } @@ -582,28 +582,28 @@ public class UserNote { [JsonProperty("targetUserId")] public ulong TargetUserId { get; set; } - + [JsonProperty("modUserId")] public ulong ModUserId { get; set; } - + [JsonProperty("noteText")] public string NoteText { get; set; } - + [JsonProperty("showOnModmail")] public bool ShowOnModmail { get; set; } - + [JsonProperty("showOnWarn")] public bool ShowOnWarn { get; set; } - + [JsonProperty("showAllMods")] public bool ShowAllMods { get; set; } - + [JsonProperty("showOnce")] public bool ShowOnce { get; set; } - + [JsonProperty("noteId")] public long NoteId { get; set; } - + [JsonProperty("timestamp")] public DateTime Timestamp { get; set; }