From f82c3024a01eda689e41a7691a7eb9a7d5c7d05d Mon Sep 17 00:00:00 2001 From: FloatingMilkshake Date: Mon, 16 Jun 2025 17:02:48 -0400 Subject: [PATCH 1/4] Add mass command group: mass ban, kick, (un)mute, (un)dehoist Includes aliases in the format used previously, like !massban Moves any mass commands that already existed into the new MassCmds class, and moves KickCmds.KickAndLogAsync and .SafeKickAndLogAsync into the new KickHelpers class Also changes BanHelpers.BanSilently to require a moderator ID; for automatic bans on member join, the bot's own ID is used to match the behavior of other automatic actions --- Commands/BanCmds.cs | 51 ------ Commands/DehoistCmds.cs | 77 -------- Commands/KickCmds.cs | 92 +--------- Commands/MassCmds.cs | 383 ++++++++++++++++++++++++++++++++++++++++ Events/MemberEvents.cs | 2 +- Helpers/BanHelpers.cs | 14 +- Helpers/KickHelpers.cs | 33 ++++ Helpers/MuteHelpers.cs | 48 +++++ 8 files changed, 479 insertions(+), 221 deletions(-) create mode 100644 Commands/MassCmds.cs create mode 100644 Helpers/KickHelpers.cs diff --git a/Commands/BanCmds.cs b/Commands/BanCmds.cs index 2642b72f..d5656d9d 100644 --- a/Commands/BanCmds.cs +++ b/Commands/BanCmds.cs @@ -177,57 +177,6 @@ public async Task BanInfoSlashCommand( await ctx.RespondAsync(embed: await BanHelpers.BanStatusEmbed(targetUser, ctx.Guild), ephemeral: !isPublic); } - [Command("massbantextcmd")] - [TextAlias("massban", "bigbonk")] - [Description("Ban multiple users from the server at once.")] - [AllowedProcessors(typeof(TextCommandProcessor))] - [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] - public async Task MassBanCmd(TextCommandContext ctx, [Description("The list of users to ban, separated by newlines or spaces, optionally followed by a reason."), RemainingText] string input) - { - List inputString = input.Replace("\n", " ").Replace("\r", "").Split(' ').ToList(); - List users = new(); - string reason = ""; - foreach (var word in inputString) - { - if (ulong.TryParse(word, out var id)) - users.Add(id); - else - reason += $"{word} "; - } - reason = reason.Trim(); - - if (users.Count == 1 || users.Count == 0) - { - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} Not accepting a massban with a single user. Please use `!ban`."); - return; - } - - List> taskList = new(); - int successes = 0; - - await ctx.RespondAsync("Processing, please wait."); - var loading = await ctx.GetResponseAsync(); - - foreach (ulong user in users) - { - if (string.IsNullOrWhiteSpace(reason)) - taskList.Add(BanSilently(ctx.Guild, user)); - else - taskList.Add(BanSilently(ctx.Guild, user, $"Mass ban: {reason}")); - } - - var tasks = await Task.WhenAll(taskList); - - foreach (var task in taskList) - { - if (task.Result) - successes += 1; - } - - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Banned} **{successes}**/{users.Count} users were banned successfully."); - await loading.DeleteAsync(); - } - [Command("bantextcmd")] [TextAlias("ban", "tempban", "bonk", "isekaitruck")] [Description("Bans a user that you have permission to ban, deleting all their messages in the process. See also: bankeep.")] diff --git a/Commands/DehoistCmds.cs b/Commands/DehoistCmds.cs index 7a978840..38f0a247 100644 --- a/Commands/DehoistCmds.cs +++ b/Commands/DehoistCmds.cs @@ -135,82 +135,5 @@ public async Task PermadehoistStatusSlashCmd(CommandContext ctx, [Parameter("mem await ctx.RespondAsync($"{Program.cfgjson.Emoji.Off} {discordUser.Mention} is not permadehoisted.", mentions: false); } } - - [Command("massdehoisttextcmd")] - [TextAlias("massdehoist")] - [Description("Dehoist everyone on the server who has a bad name. This may take a while and can exhaust rate limits.")] - [AllowedProcessors(typeof(TextCommandProcessor))] - [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] - public async Task MassDehoist(TextCommandContext ctx) - { - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it. This will take a while."); - var msg = await ctx.GetResponseAsync(); - - var (totalMembers, failedMembers) = await DehoistHelpers.MassDehoistAsync(ctx.Guild, ctx.User); - - _ = msg.DeleteAsync(); - await ctx.Channel.SendMessageAsync(new DiscordMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Success} Successfully dehoisted {totalMembers - failedMembers} of {totalMembers} member(s)! (Check Audit Log for details)").WithReply(ctx.Message.Id, true, false)); - } - - [Command("massundehoisttextcmd")] - [TextAlias("massundehoist")] - [Description("Remove the dehoist for users attached via a txt file.")] - [AllowedProcessors(typeof(TextCommandProcessor))] - [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] - public async Task MassUndhoist(TextCommandContext ctx) - { - int failedCount = 0; - - if (ctx.Message.Attachments.Count == 0) - { - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} Please upload an attachment as well."); - } - else - { - string strList; - using (HttpClient client = new()) - { - strList = await client.GetStringAsync(ctx.Message.Attachments[0].Url); - } - - var list = strList.Split(' '); - - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it. This will take a while."); - var msg = await ctx.GetResponseAsync(); - - foreach (string strID in list) - { - ulong id = Convert.ToUInt64(strID); - DiscordMember member = default; - try - { - member = await ctx.Guild.GetMemberAsync(id); - } - catch (DSharpPlus.Exceptions.NotFoundException) - { - failedCount++; - continue; - } - - if (member.DisplayName[0] == DehoistHelpers.dehoistCharacter && !member.MemberFlags.Value.HasFlag(DiscordMemberFlags.AutomodQuarantinedUsername)) - { - var newNickname = member.Nickname[1..]; - await member.ModifyAsync(a => - { - a.Nickname = newNickname; - a.AuditLogReason = $"[Mass undehoist by {DiscordHelpers.UniqueUsername(ctx.User)}]"; - } - ); - } - else - { - failedCount++; - } - } - - await msg.ModifyAsync($"{Program.cfgjson.Emoji.Success} Successfully undehoisted {list.Length - failedCount} of {list.Length} member(s)! (Check Audit Log for details)"); - - } - } } } \ No newline at end of file diff --git a/Commands/KickCmds.cs b/Commands/KickCmds.cs index 23ce2fe8..48068295 100644 --- a/Commands/KickCmds.cs +++ b/Commands/KickCmds.cs @@ -35,7 +35,7 @@ public class KickCmds { if (DiscordHelpers.AllowedToMod(await ctx.Guild.GetMemberAsync(ctx.Client.CurrentUser.Id), member)) { - await KickAndLogAsync(member, reason, ctx.Member); + await KickHelpers.KickAndLogAsync(member, reason, ctx.Member); await ctx.Channel.SendMessageAsync($"{Program.cfgjson.Emoji.Ejected} {target.Mention} has been kicked: **{reason}**"); if (ctx is SlashCommandContext) await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Done!", ephemeral: true); @@ -53,95 +53,5 @@ public class KickCmds return; } } - - [Command("masskicktextcmd")] - [TextAlias("masskick")] - [Description("Kick multiple users from the server at once.")] - [AllowedProcessors(typeof(TextCommandProcessor))] - [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] - public async Task MassKickCmd(TextCommandContext ctx, [Description("The list of users to kick, separated by newlines or spaces, optionally followed by a reason."), RemainingText] string input) - { - - List inputString = input.Replace("\n", " ").Replace("\r", "").Split(' ').ToList(); - List users = new(); - string reason = ""; - foreach (var word in inputString) - { - if (ulong.TryParse(word, out var id)) - users.Add(id); - else - reason += $"{word} "; - } - reason = reason.Trim(); - - if (users.Count == 1 || users.Count == 0) - { - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} Not accepting a masskick with a single user. Please use `!kick`."); - return; - } - - List> taskList = new(); - int successes = 0; - - await ctx.RespondAsync("Processing, please wait."); - var loading = await ctx.GetResponseAsync(); - - foreach (ulong user in users) - { - try - { - var member = await ctx.Guild.GetMemberAsync(user); - if (member is not null) - { - - taskList.Add(SafeKickAndLogAsync(member, $"Mass kick{(string.IsNullOrWhiteSpace(reason) ? "" : $": {reason}")}", ctx.Member)); - } - } - catch - { - // not successful, move on - } - } - - var tasks = await Task.WhenAll(taskList); - - foreach (var task in taskList) - { - if (task.Result) - successes += 1; - } - - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Deleted} **{successes}**/{users.Count} users were kicked successfully."); - await loading.DeleteAsync(); - } - - - public async static Task KickAndLogAsync(DiscordMember target, string reason, DiscordMember moderator) - { - await target.RemoveAsync(reason); - await LogChannelHelper.LogMessageAsync("mod", - new DiscordMessageBuilder() - .WithContent($"{Program.cfgjson.Emoji.Ejected} {target.Mention} was kicked by {moderator.Mention}.\nReason: **{reason}**") - .WithAllowedMentions(Mentions.None) - ); - } - - public async static Task SafeKickAndLogAsync(DiscordMember target, string reason, DiscordMember moderator) - { - try - { - await target.RemoveAsync(reason); - await LogChannelHelper.LogMessageAsync("mod", - new DiscordMessageBuilder() - .WithContent($"{Program.cfgjson.Emoji.Ejected} {target.Mention} was kicked by {moderator.Mention}.\nReason: **{reason}**") - .WithAllowedMentions(Mentions.None) - ); - return true; - } - catch - { - return false; - } - } } } \ No newline at end of file diff --git a/Commands/MassCmds.cs b/Commands/MassCmds.cs new file mode 100644 index 00000000..6be33559 --- /dev/null +++ b/Commands/MassCmds.cs @@ -0,0 +1,383 @@ +namespace Cliptok.Commands +{ + [Command("mass")] + [Description("Commands for performing mass actions.")] + [HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator), RequirePermissions(DiscordPermission.ModerateMembers)] + [AllowedProcessors(typeof(SlashCommandProcessor), typeof(TextCommandProcessor))] + public static class MassCmds + { + [Command("ban")] + [TextAlias("bigbonk")] + [Description("Ban multiple users from the server at once.")] + [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] + [AllowedProcessors(typeof(SlashCommandProcessor), typeof(TextCommandProcessor))] + public static async Task MassBanCmd(CommandContext ctx, [Parameter("input"), Description("The list of users to ban, separated by spaces, optionally followed by a reason."), RemainingText] string input) + { + if (ctx is SlashCommandContext) + await ctx.DeferResponseAsync(); + + var (users, reason) = ParseInput(input); + + if (users.Count == 1 || users.Count == 0) + { + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} Not accepting a massban with a single user. Please use `!ban`."); + return; + } + + List> taskList = new(); + int successes = 0; + + DiscordMessage loading = default; + if (ctx is TextCommandContext) + { + await ctx.RespondAsync("Processing, please wait."); + loading = await ctx.GetResponseAsync(); + } + + foreach (ulong user in users) + { + if (string.IsNullOrWhiteSpace(reason)) + taskList.Add(BanHelpers.BanSilently(ctx.Guild, user, ctx.User.Id)); + else + taskList.Add(BanHelpers.BanSilently(ctx.Guild, user, ctx.User.Id, $"Mass ban: {reason}")); + } + + var tasks = await Task.WhenAll(taskList); + + foreach (var task in taskList) + { + if (task.Result) + successes += 1; + } + + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Banned} **{successes}**/{users.Count} users were banned successfully."); + if (ctx is TextCommandContext) + await loading.DeleteAsync(); + } + + [Command("kick")] + [Description("Kick multiple users from the server at once.")] + [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] + [AllowedProcessors(typeof(SlashCommandProcessor), typeof(TextCommandProcessor))] + public static async Task MassKickCmd(CommandContext ctx, [Parameter("input"), Description("The list of users to kick, separated by spaces, optionally followed by a reason."), RemainingText] string input) + { + if (ctx is SlashCommandContext) + await ctx.DeferResponseAsync(); + + var (users, reason) = ParseInput(input); + + if (users.Count == 1 || users.Count == 0) + { + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} Not accepting a masskick with a single user. Please use `!kick`."); + return; + } + + List> taskList = new(); + int successes = 0; + + DiscordMessage loading = default; + if (ctx is TextCommandContext) + { + await ctx.RespondAsync("Processing, please wait."); + loading = await ctx.GetResponseAsync(); + } + + foreach (ulong user in users) + { + try + { + var member = await ctx.Guild.GetMemberAsync(user); + if (member is not null) + { + + taskList.Add(KickHelpers.SafeKickAndLogAsync(member, $"Mass kick{(string.IsNullOrWhiteSpace(reason) ? "" : $": {reason}")}", ctx.Member)); + } + } + catch + { + // not successful, move on + } + } + + var tasks = await Task.WhenAll(taskList); + + foreach (var task in taskList) + { + if (task.Result) + successes += 1; + } + + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Deleted} **{successes}**/{users.Count} users were kicked successfully."); + if (ctx is TextCommandContext) + await loading.DeleteAsync(); + } + + [Command("mute")] + [Description("Mute multiple users at once.")] + [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] + [AllowedProcessors(typeof(SlashCommandProcessor), typeof(TextCommandProcessor))] + public static async Task MassMuteCmd(CommandContext ctx, [Parameter("input"), Description("The list of users to mute, separated by spaces, optionally followed by a reason."), RemainingText] string input) + { + if (ctx is SlashCommandContext) + await ctx.DeferResponseAsync(); + + var (users, reason) = ParseInput(input); + + if (users.Count == 1 || users.Count == 0) + { + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} Not accepting a massmute with a single user. Please use `!mute`."); + return; + } + + List> taskList = new(); + int successes = 0; + + DiscordMessage loading = default; + if (ctx is TextCommandContext) + { + await ctx.RespondAsync("Processing, please wait."); + loading = await ctx.GetResponseAsync(); + } + + foreach (ulong user in users) + { + if (string.IsNullOrWhiteSpace(reason)) + taskList.Add(MuteHelpers.MuteSilently(ctx.Guild, user, ctx.User.Id)); + else + taskList.Add(MuteHelpers.MuteSilently(ctx.Guild, user, ctx.User.Id, $"Mass mute: {reason}")); + } + + var tasks = await Task.WhenAll(taskList); + + foreach (var task in taskList) + { + if (task.Result) + successes += 1; + } + + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Muted} **{successes}**/{users.Count} users were muted successfully."); + if (ctx is TextCommandContext) + await loading.DeleteAsync(); + } + + [Command("unmute")] + [Description("Unmute multiple users at once.")] + [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] + [AllowedProcessors(typeof(SlashCommandProcessor), typeof(TextCommandProcessor))] + public static async Task MassUnmuteCmd(CommandContext ctx, [Parameter("input"), Description("The list of users to unmute, separated by spaces, optionally followed by a reason."), RemainingText] string input) + { + if (ctx is SlashCommandContext) + await ctx.DeferResponseAsync(); + + var (users, reason) = ParseInput(input); + + if (users.Count == 1 || users.Count == 0) + { + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} Not accepting a massunmute with a single user. Please use `!unmute`."); + return; + } + + List> taskList = new(); + int successes = 0; + + DiscordMessage loading = default; + if (ctx is TextCommandContext) + { + await ctx.RespondAsync("Processing, please wait."); + loading = await ctx.GetResponseAsync(); + } + + foreach (ulong user in users) + { + if (string.IsNullOrWhiteSpace(reason)) + taskList.Add(MuteHelpers.UnmuteSilently(ctx.Guild, user)); + else + taskList.Add(MuteHelpers.UnmuteSilently(ctx.Guild, user, $"Mass unmute: {reason}")); + } + + var tasks = await Task.WhenAll(taskList); + + foreach (var task in taskList) + { + if (task.Result) + successes += 1; + } + + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} **{successes}**/{users.Count} users were unmuted successfully."); + if (ctx is TextCommandContext) + await loading.DeleteAsync(); + } + + [Command("dehoist")] + [Description("Dehoist everyone on the server with a bad name. This may take a while and can exhaust rate limits.")] + [AllowedProcessors(typeof(SlashCommandProcessor), typeof(TextCommandProcessor))] + [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] + public static async Task MassDehoist(CommandContext ctx) + { + DiscordMessage msg = default; + if (ctx is SlashCommandContext) + await ctx.DeferResponseAsync(); + else + { + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it. This will take a while."); + msg = await ctx.GetResponseAsync(); + } + + var (totalMembers, failedMembers) = await DehoistHelpers.MassDehoistAsync(ctx.Guild, ctx.User); + + if (ctx is TextCommandContext) + _ = msg.DeleteAsync(); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully dehoisted {totalMembers - failedMembers} of {totalMembers} member(s)! (Check Audit Log for details)"); + } + + [Command("undehoist")] + [Description("Remove the dehoist for users attached via a txt file.")] + [AllowedProcessors(typeof(SlashCommandProcessor), typeof(TextCommandProcessor))] + [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] + public static async Task MassUndehoist(CommandContext ctx, [Parameter("file"), Description("A text file containing the list of users to undehoist.")] DiscordAttachment file = null) + { + DiscordAttachment attachment; + if (ctx is TextCommandContext tctx && tctx.Message.Attachments.Count > 0) + attachment = tctx.Message.Attachments[0]; + else + attachment = file; + + int failedCount = 0; + + if (attachment is null) + { + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} Please upload an attachment as well."); + } + else + { + string strList; + using (HttpClient client = new()) + { + strList = await client.GetStringAsync(attachment.Url); + } + + var list = strList.Split(' '); + + DiscordMessage msg = default; + if (ctx is SlashCommandContext) + await ctx.DeferResponseAsync(); + else + { + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it. This will take a while."); + msg = await ctx.GetResponseAsync(); + } + + foreach (string strID in list) + { + ulong id = Convert.ToUInt64(strID); + DiscordMember member = default; + try + { + member = await ctx.Guild.GetMemberAsync(id); + } + catch (DSharpPlus.Exceptions.NotFoundException) + { + failedCount++; + continue; + } + + if (member.DisplayName[0] == DehoistHelpers.dehoistCharacter && !member.MemberFlags.Value.HasFlag(DiscordMemberFlags.AutomodQuarantinedUsername)) + { + var newNickname = member.Nickname[1..]; + await member.ModifyAsync(a => + { + a.Nickname = newNickname; + a.AuditLogReason = $"[Mass undehoist by {DiscordHelpers.UniqueUsername(ctx.User)}]"; + } + ); + } + else + { + failedCount++; + } + } + + if (ctx is TextCommandContext) + await msg.DeleteAsync(); + + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully undehoisted {list.Length - failedCount} of {list.Length} member(s)! (Check Audit Log for details)"); + + } + } + + private static (List users, string reason) ParseInput(string input) + { + List inputString = input.Replace("\n", " ").Replace("\r", "").Split(' ').ToList(); + List users = new(); + string reason = ""; + foreach (var word in inputString) + { + if (ulong.TryParse(word, out var id)) + users.Add(id); + else + reason += $"{word} "; + } + reason = reason.Trim(); + + return (users, reason); + } + } + + // An attempt at aliases... + public static class MassTextAliases + { + [Command("massban")] + [Description("Ban multiple users from the server at once.")] + [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] + [AllowedProcessors(typeof(TextCommandProcessor))] + public static async Task MassBanAliasCmd(TextCommandContext ctx, [Description("The list of users to ban, separated by newlines or spaces, optionally followed by a reason."), RemainingText] string input) + { + await MassCmds.MassBanCmd(ctx, input); + } + + [Command("masskick")] + [Description("Kick multiple users from the server at once.")] + [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] + [AllowedProcessors(typeof(TextCommandProcessor))] + public static async Task MassKickAliasCmd(TextCommandContext ctx, [Description("The list of users to kick, separated by newlines or spaces, optionally followed by a reason."), RemainingText] string input) + { + await MassCmds.MassKickCmd(ctx, input); + } + + [Command("massmute")] + [Description("Mute multiple users at once.")] + [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] + [AllowedProcessors(typeof(TextCommandProcessor))] + public static async Task MassMuteAliasCmd(CommandContext ctx, [Description("The list of users to mute, separated by newlines or spaces, optionally followed by a reason."), RemainingText] string input) + { + await MassCmds.MassMuteCmd(ctx, input); + } + + [Command("massunmute")] + [Description("Unmute multiple users at once.")] + [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] + [AllowedProcessors(typeof(TextCommandProcessor))] + public static async Task MassUnmuteAliasCmd(CommandContext ctx, [Description("The list of users to mute, separated by newlines or spaces, optionally followed by a reason."), RemainingText] string input) + { + await MassCmds.MassUnmuteCmd(ctx, input); + } + + [Command("massdehoist")] + [Description("Dehoist everyone on the server with a bad name. This may take a while and can exhaust rate limits.")] + [AllowedProcessors(typeof(TextCommandProcessor))] + [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] + public static async Task MassDehoistAliasCmd(TextCommandContext ctx) + { + await MassCmds.MassDehoist(ctx); + } + + [Command("massundehoist")] + [Description("Remove the dehoist for users attached via a txt file.")] + [AllowedProcessors(typeof(TextCommandProcessor))] + [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] + public static async Task MassUndehoistAliasCmd(TextCommandContext ctx) + { + await MassCmds.MassUndehoist(ctx); + } + } +} \ No newline at end of file diff --git a/Events/MemberEvents.cs b/Events/MemberEvents.cs index 47d4cb3e..622790e8 100644 --- a/Events/MemberEvents.cs +++ b/Events/MemberEvents.cs @@ -71,7 +71,7 @@ public static async Task GuildMemberAdded(DiscordClient client, GuildMemberAdded { if (avatars.Contains(e.Member.AvatarHash)) { - var _ = BanHelpers.BanSilently(e.Guild, e.Member.Id, "Secret sauce"); + var _ = BanHelpers.BanSilently(e.Guild, e.Member.Id, client.CurrentUser.Id, "Secret sauce"); await LogChannelHelper.LogMessageAsync("investigations", $"{cfgjson.Emoji.Banned} Raid-banned {e.Member.Mention} for matching avatar: {e.Member.AvatarUrl.Replace("1024", "128")}"); } } diff --git a/Helpers/BanHelpers.cs b/Helpers/BanHelpers.cs index e02f11b9..ca703795 100644 --- a/Helpers/BanHelpers.cs +++ b/Helpers/BanHelpers.cs @@ -199,11 +199,23 @@ public async static Task UnbanUserAsync(DiscordGuild guild, DiscordUser ta return true; } - public static async Task BanSilently(DiscordGuild targetGuild, ulong targetUserId, string reason = "Mass ban") + public static async Task BanSilently(DiscordGuild targetGuild, ulong targetUserId, ulong moderatorId, string reason = "Mass ban") { try { await targetGuild.BanMemberAsync(targetUserId, TimeSpan.FromDays(7), reason); + + MemberPunishment newBan = new() + { + MemberId = targetUserId, + ModId = moderatorId, + ServerId = targetGuild.Id, + ExpireTime = null, + ActionTime = DateTime.Now, + Reason = reason + }; + + await Program.db.HashSetAsync("bans", targetUserId, JsonConvert.SerializeObject(newBan)); // Remove user message tracking if (await Program.db.SetContainsAsync("trackedUsers", targetUserId)) diff --git a/Helpers/KickHelpers.cs b/Helpers/KickHelpers.cs new file mode 100644 index 00000000..ded82ed0 --- /dev/null +++ b/Helpers/KickHelpers.cs @@ -0,0 +1,33 @@ +namespace Cliptok.Helpers +{ + public class KickHelpers + { + public async static Task KickAndLogAsync(DiscordMember target, string reason, DiscordMember moderator) + { + await target.RemoveAsync(reason); + await LogChannelHelper.LogMessageAsync("mod", + new DiscordMessageBuilder() + .WithContent($"{Program.cfgjson.Emoji.Ejected} {target.Mention} was kicked by {moderator.Mention}.\nReason: **{reason}**") + .WithAllowedMentions(Mentions.None) + ); + } + + public async static Task SafeKickAndLogAsync(DiscordMember target, string reason, DiscordMember moderator) + { + try + { + await target.RemoveAsync(reason); + await LogChannelHelper.LogMessageAsync("mod", + new DiscordMessageBuilder() + .WithContent($"{Program.cfgjson.Emoji.Ejected} {target.Mention} was kicked by {moderator.Mention}.\nReason: **{reason}**") + .WithAllowedMentions(Mentions.None) + ); + return true; + } + catch + { + return false; + } + } + } +} \ No newline at end of file diff --git a/Helpers/MuteHelpers.cs b/Helpers/MuteHelpers.cs index 5f69031c..1f003021 100644 --- a/Helpers/MuteHelpers.cs +++ b/Helpers/MuteHelpers.cs @@ -318,6 +318,54 @@ public static (int MuteHours, int WarnsSinceThreshold) GetHoursToMuteFor(Diction return output; } + + public static async Task MuteSilently(DiscordGuild targetGuild, ulong targetUserId, ulong moderatorId, string reason = "Mass mute") + { + try + { + // this should pull from cache most of the time! + var mutedRole = await targetGuild.GetRoleAsync(Program.cfgjson.MutedRole); + var member = await targetGuild.GetMemberAsync(targetUserId); + await member.GrantRoleAsync(mutedRole, reason); + + MemberPunishment newMute = new() + { + MemberId = targetUserId, + ModId = moderatorId, + ServerId = targetGuild.Id, + ExpireTime = null, + ActionTime = DateTime.Now, + Reason = reason + }; + + await Program.db.HashSetAsync("mutes", targetUserId, JsonConvert.SerializeObject(newMute)); + + return true; + } + catch + { + return false; + } + } + + public static async Task UnmuteSilently(DiscordGuild targetGuild, ulong targetUserId, string reason = "Mass unmute") + { + try + { + // this should pull from cache most of the time! + var mutedRole = await targetGuild.GetRoleAsync(Program.cfgjson.MutedRole); + var member = await targetGuild.GetMemberAsync(targetUserId); + await member.RevokeRoleAsync(mutedRole, reason); + + await Program.db.HashDeleteAsync("mutes", targetUserId); + + return true; + } + catch + { + return false; + } + } public static async Task UnmuteUserAsync(DiscordUser targetUser, string reason = "", bool manual = true, DiscordUser modUser = default, bool isTqsUnmute = false) { From 75ebb8777fa227e6b0235fb1ade1bbae7e45ef79 Mon Sep 17 00:00:00 2001 From: FloatingMilkshake Date: Mon, 16 Jun 2025 17:44:10 -0400 Subject: [PATCH 2/4] Add [Hidden] attribute to hide text cmds from help; hide mass aliases --- Attributes/HiddenAttribute.cs | 5 +++++ Commands/GlobalCmds.cs | 3 ++- Commands/MassCmds.cs | 6 ++++++ GlobalUsings.cs | 3 ++- 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 Attributes/HiddenAttribute.cs diff --git a/Attributes/HiddenAttribute.cs b/Attributes/HiddenAttribute.cs new file mode 100644 index 00000000..0ba5930e --- /dev/null +++ b/Attributes/HiddenAttribute.cs @@ -0,0 +1,5 @@ +namespace Cliptok.Attributes +{ + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public class HiddenAttribute : Attribute; +} \ No newline at end of file diff --git a/Commands/GlobalCmds.cs b/Commands/GlobalCmds.cs index 6c9c027c..d3fba679 100644 --- a/Commands/GlobalCmds.cs +++ b/Commands/GlobalCmds.cs @@ -23,7 +23,8 @@ public static async Task Help(CommandContext ctx, [Description("Command to provi IEnumerable cmds = ctx.Extension.Commands.Values.Where(cmd => cmd.Attributes.Any(attr => attr is AllowedProcessorsAttribute apAttr - && apAttr.Processors.Contains(typeof(TextCommandProcessor)))); + && apAttr.Processors.Contains(typeof(TextCommandProcessor))) + && !cmd.Attributes.Any(attr => attr is HiddenAttribute && command == "")); if (commandSplit.Length != 0 && commandSplit[0] != "") { diff --git a/Commands/MassCmds.cs b/Commands/MassCmds.cs index 6be33559..f8aaadb8 100644 --- a/Commands/MassCmds.cs +++ b/Commands/MassCmds.cs @@ -330,6 +330,7 @@ public static class MassTextAliases [Description("Ban multiple users from the server at once.")] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] [AllowedProcessors(typeof(TextCommandProcessor))] + [Hidden] public static async Task MassBanAliasCmd(TextCommandContext ctx, [Description("The list of users to ban, separated by newlines or spaces, optionally followed by a reason."), RemainingText] string input) { await MassCmds.MassBanCmd(ctx, input); @@ -339,6 +340,7 @@ public static async Task MassBanAliasCmd(TextCommandContext ctx, [Description("T [Description("Kick multiple users from the server at once.")] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] [AllowedProcessors(typeof(TextCommandProcessor))] + [Hidden] public static async Task MassKickAliasCmd(TextCommandContext ctx, [Description("The list of users to kick, separated by newlines or spaces, optionally followed by a reason."), RemainingText] string input) { await MassCmds.MassKickCmd(ctx, input); @@ -348,6 +350,7 @@ public static async Task MassKickAliasCmd(TextCommandContext ctx, [Description(" [Description("Mute multiple users at once.")] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] [AllowedProcessors(typeof(TextCommandProcessor))] + [Hidden] public static async Task MassMuteAliasCmd(CommandContext ctx, [Description("The list of users to mute, separated by newlines or spaces, optionally followed by a reason."), RemainingText] string input) { await MassCmds.MassMuteCmd(ctx, input); @@ -357,6 +360,7 @@ public static async Task MassMuteAliasCmd(CommandContext ctx, [Description("The [Description("Unmute multiple users at once.")] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] [AllowedProcessors(typeof(TextCommandProcessor))] + [Hidden] public static async Task MassUnmuteAliasCmd(CommandContext ctx, [Description("The list of users to mute, separated by newlines or spaces, optionally followed by a reason."), RemainingText] string input) { await MassCmds.MassUnmuteCmd(ctx, input); @@ -366,6 +370,7 @@ public static async Task MassUnmuteAliasCmd(CommandContext ctx, [Description("Th [Description("Dehoist everyone on the server with a bad name. This may take a while and can exhaust rate limits.")] [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] + [Hidden] public static async Task MassDehoistAliasCmd(TextCommandContext ctx) { await MassCmds.MassDehoist(ctx); @@ -375,6 +380,7 @@ public static async Task MassDehoistAliasCmd(TextCommandContext ctx) [Description("Remove the dehoist for users attached via a txt file.")] [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] + [Hidden] public static async Task MassUndehoistAliasCmd(TextCommandContext ctx) { await MassCmds.MassUndehoist(ctx); diff --git a/GlobalUsings.cs b/GlobalUsings.cs index 16e89996..77bf1e85 100644 --- a/GlobalUsings.cs +++ b/GlobalUsings.cs @@ -1,4 +1,5 @@ -global using Cliptok.CommandChecks; +global using Cliptok.Attributes; +global using Cliptok.CommandChecks; global using Cliptok.Enums; global using Cliptok.Events; global using Cliptok.Helpers; From f906d765dabbd344dafa3893d0154d9b1f263cfd Mon Sep 17 00:00:00 2001 From: FloatingMilkshake Date: Sun, 13 Jul 2025 15:43:54 -0400 Subject: [PATCH 3/4] Program.db -> Program.redis --- Helpers/BanHelpers.cs | 2 +- Helpers/MuteHelpers.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Helpers/BanHelpers.cs b/Helpers/BanHelpers.cs index 4f510336..963be2ff 100644 --- a/Helpers/BanHelpers.cs +++ b/Helpers/BanHelpers.cs @@ -215,7 +215,7 @@ public static async Task BanSilently(DiscordGuild targetGuild, ulong targe Reason = reason }; - await Program.db.HashSetAsync("bans", targetUserId, JsonConvert.SerializeObject(newBan)); + await Program.redis.HashSetAsync("bans", targetUserId, JsonConvert.SerializeObject(newBan)); // Remove user message tracking if (await Program.redis.SetContainsAsync("trackedUsers", targetUserId)) diff --git a/Helpers/MuteHelpers.cs b/Helpers/MuteHelpers.cs index b459b9c1..10e07064 100644 --- a/Helpers/MuteHelpers.cs +++ b/Helpers/MuteHelpers.cs @@ -338,7 +338,7 @@ public static async Task MuteSilently(DiscordGuild targetGuild, ulong targ Reason = reason }; - await Program.db.HashSetAsync("mutes", targetUserId, JsonConvert.SerializeObject(newMute)); + await Program.redis.HashSetAsync("mutes", targetUserId, JsonConvert.SerializeObject(newMute)); return true; } @@ -357,7 +357,7 @@ public static async Task UnmuteSilently(DiscordGuild targetGuild, ulong ta var member = await targetGuild.GetMemberAsync(targetUserId); await member.RevokeRoleAsync(mutedRole, reason); - await Program.db.HashDeleteAsync("mutes", targetUserId); + await Program.redis.HashDeleteAsync("mutes", targetUserId); return true; } From b071a619d21aee069e78b166aa37af00fb10dc84 Mon Sep 17 00:00:00 2001 From: FloatingMilkshake Date: Wed, 6 Aug 2025 12:21:59 -0400 Subject: [PATCH 4/4] Accept mentions --- Commands/MassCmds.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Commands/MassCmds.cs b/Commands/MassCmds.cs index f8aaadb8..41bfe2b8 100644 --- a/Commands/MassCmds.cs +++ b/Commands/MassCmds.cs @@ -312,7 +312,7 @@ private static (List users, string reason) ParseInput(string input) string reason = ""; foreach (var word in inputString) { - if (ulong.TryParse(word, out var id)) + if (ulong.TryParse(word.Replace("<@", "").Replace(">", ""), out var id)) users.Add(id); else reason += $"{word} ";