diff --git a/Boyfriend/Boyfriend.cs b/Boyfriend/Boyfriend.cs index 876bd20..77607fb 100644 --- a/Boyfriend/Boyfriend.cs +++ b/Boyfriend/Boyfriend.cs @@ -133,6 +133,6 @@ public static class Boyfriend { return guild; } - throw new Exception(Messages.CouldntFindGuildByChannel); + throw new Exception("Could not find guild by channel!"); } } diff --git a/Boyfriend/Boyfriend.csproj b/Boyfriend/Boyfriend.csproj index 8839dbb..a37a533 100644 --- a/Boyfriend/Boyfriend.csproj +++ b/Boyfriend/Boyfriend.csproj @@ -18,7 +18,6 @@ - diff --git a/Boyfriend/CommandHandler.cs b/Boyfriend/CommandHandler.cs deleted file mode 100644 index a8eed4d..0000000 --- a/Boyfriend/CommandHandler.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System.Text; -using System.Text.RegularExpressions; -using Boyfriend.Commands; -using Discord; -using Discord.Commands; -using Discord.WebSocket; - -namespace Boyfriend; - -public static class CommandHandler { - public static readonly Command[] Commands = { - new BanCommand(), new ClearCommand(), new HelpCommand(), - new KickCommand(), new MuteCommand(), new PingCommand(), - new SettingsCommand(), new UnbanCommand(), new UnmuteCommand() - }; - - private static readonly Dictionary RegexCache = new(); - private static readonly Regex MentionRegex = new(Regex.Escape("<@855023234407333888>")); - - public static readonly StringBuilder StackedReplyMessage = new(); - public static readonly StringBuilder StackedPublicFeedback = new(); - public static readonly StringBuilder StackedPrivateFeedback = new(); - -#pragma warning disable CA2211 - public static bool ConfigWriteScheduled = false; // Can't be private -#pragma warning restore CA2211 - - private static bool _handlerBusy; - - public static async Task HandleCommand(SocketUserMessage message) { - while (_handlerBusy) await Task.Delay(200); - _handlerBusy = true; - StackedReplyMessage.Clear(); - StackedPrivateFeedback.Clear(); - StackedPublicFeedback.Clear(); - var context = new SocketCommandContext(Boyfriend.Client, message); - var guild = context.Guild; - var config = Boyfriend.GetGuildConfig(guild.Id); - - Regex regex; - if (RegexCache.ContainsKey(config["Prefix"])) { regex = RegexCache[config["Prefix"]]; } else { - regex = new Regex(Regex.Escape(config["Prefix"])); - RegexCache.Add(config["Prefix"], regex); - } - - var list = message.Content.Split("\n"); - var currentLine = 0; - foreach (var line in list) { - currentLine++; - await RunCommands(line, regex, context, currentLine == list.Length); - } - - if (ConfigWriteScheduled) await Boyfriend.WriteGuildConfig(guild.Id); - - var adminChannel = Utils.GetAdminLogChannel(guild.Id); - var systemChannel = guild.SystemChannel; - if (StackedPrivateFeedback.Length > 0 && adminChannel != null && adminChannel.Id != message.Channel.Id) - await Utils.SilentSendAsync(adminChannel, StackedPrivateFeedback.ToString()); - if (StackedPublicFeedback.Length > 0 && systemChannel != null && systemChannel.Id != adminChannel?.Id - && systemChannel.Id != message.Channel.Id) - await Utils.SilentSendAsync(systemChannel, StackedPublicFeedback.ToString()); - _handlerBusy = false; - } - - private static async Task RunCommands(string line, Regex regex, SocketCommandContext context, bool shouldAwait) { - foreach (var command in Commands) { - var lineNoMention = MentionRegex.Replace(line, "", 1); - if (!command.Aliases.Contains(regex.Replace(lineNoMention, "", 1).Trim().ToLower().Split()[0])) - continue; - - await context.Channel.TriggerTypingAsync(); - - var args = line.Split().Skip(1).ToArray(); - - if (command.ArgsLengthRequired <= args.Length) - if (shouldAwait) - await command.Run(context, args); - else - _ = command.Run(context, args); - else - StackedReplyMessage.AppendFormat(Messages.NotEnoughArguments, command.ArgsLengthRequired.ToString(), - args.Length.ToString()).AppendLine(); - if (StackedReplyMessage.Length <= 1675 && !shouldAwait) continue; - await context.Message.ReplyAsync(StackedReplyMessage.ToString(), false, null, AllowedMentions.None); - StackedReplyMessage.Clear(); - } - } - - public static string HasPermission(ref SocketGuildUser user, GuildPermission toCheck, - GuildPermission forBot = GuildPermission.StartEmbeddedActivities) { - var me = user.Guild.CurrentUser; - - if (user.Id == user.Guild.OwnerId || (me.GuildPermissions.Has(GuildPermission.Administrator) && - user.GuildPermissions.Has(GuildPermission.Administrator))) return ""; - - if (forBot == GuildPermission.StartEmbeddedActivities) forBot = toCheck; - - if (!me.GuildPermissions.Has(forBot)) - return Messages.CommandNoPermissionBot; - - return !user.GuildPermissions.Has(toCheck) ? Messages.CommandNoPermissionUser : ""; - } - - public static string CanInteract(ref SocketGuildUser actor, ref SocketGuildUser target) { - if (actor.Guild != target.Guild) - return Messages.InteractionsDifferentGuilds; - if (actor.Id == actor.Guild.OwnerId) - return ""; - - if (target.Id == target.Guild.OwnerId) - return Messages.InteractionsOwner; - if (actor == target) - return Messages.InteractionsYourself; - - var me = target.Guild.CurrentUser; - - if (target == me) - return Messages.InteractionsMe; - if (me.Hierarchy <= target.Hierarchy) - return Messages.InteractionsFailedBot; - - return actor.Hierarchy <= target.Hierarchy ? Messages.InteractionsFailedUser : ""; - } -} diff --git a/Boyfriend/CommandProcessor.cs b/Boyfriend/CommandProcessor.cs new file mode 100644 index 0000000..2d83b54 --- /dev/null +++ b/Boyfriend/CommandProcessor.cs @@ -0,0 +1,271 @@ +using System.Text; +using System.Text.RegularExpressions; +using Boyfriend.Commands; +using Discord; +using Discord.Commands; +using Discord.WebSocket; + +namespace Boyfriend; + +public class CommandProcessor { + private const string Success = ":white_check_mark: "; + private const string MissingArgument = ":keyboard: "; + private const string InvalidArgument = ":construction: "; + private const string NoAccess = ":no_entry_sign: "; + private const string CantInteract = ":vertical_traffic_light: "; + + public static readonly Command[] Commands = { + new BanCommand(), new ClearCommand(), new HelpCommand(), + new KickCommand(), new MuteCommand(), new PingCommand(), + new SettingsCommand(), new UnbanCommand(), new UnmuteCommand() + }; + + private static readonly Dictionary RegexCache = new(); + private static readonly Regex MentionRegex = new(Regex.Escape("<@855023234407333888>")); + private readonly StringBuilder _stackedPrivateFeedback = new(); + private readonly StringBuilder _stackedPublicFeedback = new(); + private readonly StringBuilder _stackedReplyMessage = new(); + private readonly List _tasks = new(); + + public readonly SocketCommandContext Context; + + public bool ConfigWriteScheduled = false; + + public CommandProcessor(SocketUserMessage message) { + Context = new SocketCommandContext(Boyfriend.Client, message); + } + + public async Task HandleCommand() { + _stackedReplyMessage.Clear(); + _stackedPrivateFeedback.Clear(); + _stackedPublicFeedback.Clear(); + var guild = Context.Guild; + var config = Boyfriend.GetGuildConfig(guild.Id); + + if (GetMember().Roles.Contains(Utils.GetMuteRole(guild))) { + await Context.Message.ReplyAsync(Messages.UserCannotUnmuteThemselves); + return; + } + + Regex regex; + if (RegexCache.ContainsKey(config["Prefix"])) { regex = RegexCache[config["Prefix"]]; } else { + regex = new Regex(Regex.Escape(config["Prefix"])); + RegexCache.Add(config["Prefix"], regex); + } + + var list = Context.Message.Content.Split("\n"); + foreach (var line in list) { + RunCommandOnLine(line, regex); + if (_stackedReplyMessage.Length > 0) _ = Context.Channel.TriggerTypingAsync(); + } + + await Task.WhenAll(_tasks); + + if (ConfigWriteScheduled) await Boyfriend.WriteGuildConfig(guild.Id); + + if (_stackedReplyMessage.Length > 0) _ = Context.Message.ReplyAsync(_stackedReplyMessage.ToString()); + + var adminChannel = Utils.GetAdminLogChannel(guild.Id); + var systemChannel = guild.SystemChannel; + if (_stackedPrivateFeedback.Length > 0 && adminChannel != null && adminChannel.Id != Context.Message.Channel.Id) + _ = Utils.SilentSendAsync(adminChannel, _stackedPrivateFeedback.ToString()); + if (_stackedPublicFeedback.Length > 0 && systemChannel != null && systemChannel.Id != adminChannel?.Id + && systemChannel.Id != Context.Message.Channel.Id) + _ = Utils.SilentSendAsync(systemChannel, _stackedPublicFeedback.ToString()); + } + + private void RunCommandOnLine(string line, Regex regex) { + foreach (var command in Commands) { + var lineNoMention = regex.Replace(MentionRegex.Replace(line, "", 1), "", 1); + if (lineNoMention == line + || !command.Aliases.Contains(regex.Replace(lineNoMention, "", 1).Trim().ToLower().Split()[0])) + continue; + + var args = line.Split().Skip(1).ToArray(); + _tasks.Add(command.Run(this, args)); + } + } + + public void Reply(string response, string? customEmoji = null) { + Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{customEmoji ?? Success}{response}", Context.Message); + } + + public void Audit(string action, bool isPublic = true) { + var format = string.Format(Messages.FeedbackFormat, Context.User.Mention, action); + if (isPublic) Utils.SafeAppendToBuilder(_stackedPublicFeedback, format, Context.Guild.SystemChannel); + Utils.SafeAppendToBuilder(_stackedPrivateFeedback, format, Utils.GetAdminLogChannel(Context.Guild.Id)); + } + + public string? GetRemaining(string[] from, int startIndex, string? argument) { + if (startIndex >= from.Length && argument != null) + Utils.SafeAppendToBuilder(_stackedReplyMessage, + $"{MissingArgument}{Utils.GetMessage($"Missing{argument}")}", Context.Message); + else + return string.Join(" ", from, startIndex, from.Length - startIndex); + return null; + } + + public SocketUser? GetUser(string[] args, int index, string? argument) { + if (index >= args.Length) { + Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{MissingArgument}{Messages.MissingUser}", + Context.Message); + return null; + } + + var user = Utils.ParseUser(args[index]); + if (user == null && argument != null) + Utils.SafeAppendToBuilder(_stackedReplyMessage, + $"{InvalidArgument}{string.Format(Messages.InvalidUser, args[index])}", Context.Message); + return user; + } + + public bool HasPermission(GuildPermission permission) { + if (!Context.Guild.CurrentUser.GuildPermissions.Has(permission)) { + Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{NoAccess}{Utils.GetMessage($"BotCannot{permission}")}", + Context.Message); + return false; + } + + if (Context.Guild.GetUser(Context.User.Id).GuildPermissions.Has(permission) + || Context.Guild.Owner.Id == Context.User.Id) return true; + + Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{NoAccess}{Utils.GetMessage($"UserCannot{permission}")}", + Context.Message); + return false; + } + + public SocketGuildUser? GetMember(SocketUser user, string? argument) { + var member = Context.Guild.GetUser(user.Id); + if (member == null && argument != null) + Utils.SafeAppendToBuilder(_stackedReplyMessage, $":x: {Messages.UserNotInGuild}", Context.Message); + return member; + } + + public SocketGuildUser? GetMember(string[] args, int index, string? argument) { + if (index >= args.Length) { + Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{MissingArgument}{Messages.MissingMember}", + Context.Message); + return null; + } + + var member = Context.Guild.GetUser(Utils.ParseMention(args[index])); + if (member == null && argument != null) + Utils.SafeAppendToBuilder(_stackedReplyMessage, + $"{InvalidArgument}{string.Format(Messages.InvalidMember, Utils.Wrap(args[index]))}", Context.Message); + return member; + } + + private SocketGuildUser GetMember() { + return Context.Guild.GetUser(Context.User.Id); + } + + public ulong? GetBan(string[] args, int index) { + if (index >= args.Length) { + Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{MissingArgument}{Messages.MissingUser}", + Context.Message); + return null; + } + + var id = Utils.ParseMention(args[index]); + if (Context.Guild.GetBanAsync(id) != null) return id; + Utils.SafeAppendToBuilder(_stackedReplyMessage, Messages.UserNotBanned, Context.Message); + return null; + } + + public int? GetNumberRange(string[] args, int index, int min, int max, string? argument) { + if (index >= args.Length) { + Utils.SafeAppendToBuilder(_stackedReplyMessage, + $"{MissingArgument}{string.Format(Messages.MissingNumber, min.ToString(), max.ToString())}", + Context.Message); + return null; + } + + if (!int.TryParse(args[index], out var i)) { + Utils.SafeAppendToBuilder(_stackedReplyMessage, + $"{InvalidArgument}{string.Format(Utils.GetMessage($"{argument}Invalid"), min.ToString(), max.ToString(), Utils.Wrap(args[index]))}", + Context.Message); + return null; + } + + if (argument == null) return i; + if (i < min) { + Utils.SafeAppendToBuilder(_stackedReplyMessage, + $"{InvalidArgument}{string.Format(Utils.GetMessage($"{argument}TooSmall"), min.ToString())}", + Context.Message); + return null; + } + + if (i <= max) return i; + Utils.SafeAppendToBuilder(_stackedReplyMessage, + $"{InvalidArgument}{string.Format(Utils.GetMessage($"{argument}TooLarge"), max.ToString())}", + Context.Message); + return null; + } + + public static TimeSpan GetTimeSpan(string[] args, int index) { + var infinity = TimeSpan.FromMilliseconds(-1); + if (index >= args.Length) + return infinity; + var chars = args[index].AsSpan(); + var numberBuilder = Boyfriend.StringBuilder; + int days = 0, hours = 0, minutes = 0, seconds = 0; + foreach (var c in chars) + if (char.IsDigit(c)) { numberBuilder.Append(c); } else { + if (numberBuilder.Length == 0) return infinity; + switch (c) { + case 'd' or 'D' or 'д' or 'Д': + days += int.Parse(numberBuilder.ToString()); + numberBuilder.Clear(); + break; + case 'h' or 'H' or 'ч' or 'Ч': + hours += int.Parse(numberBuilder.ToString()); + numberBuilder.Clear(); + break; + case 'm' or 'M' or 'м' or 'М': + minutes += int.Parse(numberBuilder.ToString()); + numberBuilder.Clear(); + break; + case 's' or 'S' or 'с' or 'С': + seconds += int.Parse(numberBuilder.ToString()); + numberBuilder.Clear(); + break; + default: return infinity; + } + } + + numberBuilder.Clear(); + return new TimeSpan(days, hours, minutes, seconds); + } + + public bool CanInteractWith(SocketGuildUser user, string action) { + if (Context.Guild.Owner.Id == Context.User.Id) return true; + if (Context.Guild.Owner.Id == user.Id) { + Utils.SafeAppendToBuilder(_stackedReplyMessage, + $"{CantInteract}{Utils.GetMessage($"UserCannot{action}Owner")}", Context.Message); + return false; + } + + if (Context.User.Id == user.Id) { + Utils.SafeAppendToBuilder(_stackedReplyMessage, + $"{CantInteract}{Utils.GetMessage($"UserCannot{action}Themselves")}", Context.Message); + return false; + } + + if (Context.Guild.CurrentUser.Id == user.Id) { + Utils.SafeAppendToBuilder(_stackedReplyMessage, + $"{CantInteract}{Utils.GetMessage($"UserCannot{action}Bot")}", Context.Message); + return false; + } + + if (Context.Guild.CurrentUser.Hierarchy <= user.Hierarchy) { + Utils.SafeAppendToBuilder(_stackedReplyMessage, + $"{CantInteract}{Utils.GetMessage($"BotCannot{action}Target")}", Context.Message); + return false; + } + + if (GetMember().Hierarchy > user.Hierarchy) return true; + Utils.SafeAppendToBuilder(_stackedReplyMessage, + $"{CantInteract}{Utils.GetMessage($"UserCannot{action}Target")}", Context.Message); + return false; + } +} diff --git a/Boyfriend/Commands/BanCommand.cs b/Boyfriend/Commands/BanCommand.cs index 0f67c81..7e42b6a 100644 --- a/Boyfriend/Commands/BanCommand.cs +++ b/Boyfriend/Commands/BanCommand.cs @@ -1,74 +1,44 @@ using Discord; -using Discord.Commands; using Discord.WebSocket; namespace Boyfriend.Commands; public class BanCommand : Command { public override string[] Aliases { get; } = { "ban", "бан" }; - public override int ArgsLengthRequired => 2; - public override async Task Run(SocketCommandContext context, string[] args) { - var toBan = Utils.ParseUser(args[0]); + public override async Task Run(CommandProcessor cmd, string[] args) { + var toBan = cmd.GetUser(args, 0, "ToBan"); + if (toBan == null || !cmd.HasPermission(GuildPermission.BanMembers)) return; - if (toBan == null) { - Error(Messages.UserDoesntExist, false); - return; - } + var memberToBan = cmd.GetMember(toBan, null); + if (memberToBan != null && !cmd.CanInteractWith(memberToBan, "Ban")) return; - var guild = context.Guild; - var author = (SocketGuildUser)context.User; + var duration = CommandProcessor.GetTimeSpan(args, 1); + var reason = cmd.GetRemaining(args, duration.TotalSeconds < 1 ? 1 : 2, "BanReason"); + if (reason == null) return; - var permissionCheckResponse = CommandHandler.HasPermission(ref author, GuildPermission.BanMembers); - if (permissionCheckResponse is not "") { - Error(permissionCheckResponse, true); - return; - } - - var reason = Utils.JoinString(ref args, 2); - var memberToBan = Utils.ParseMember(guild, args[0]); - - if (memberToBan != null) { - var interactionCheckResponse = CommandHandler.CanInteract(ref author, ref memberToBan); - if (interactionCheckResponse is not "") { - Error(interactionCheckResponse, true); - return; - } - } - - var duration = Utils.GetTimeSpan(ref args[1]) ?? TimeSpan.FromMilliseconds(-1); - if (duration.TotalSeconds < 0) { - Warn(Messages.DurationParseFailed); - reason = Utils.JoinString(ref args, 1); - - if (reason is "") { - Error(Messages.ReasonRequired, false); - return; - } - } - - await BanUser(guild, author, toBan, duration, reason); + await BanUser(cmd, toBan, duration, reason); } - public static async Task BanUser(SocketGuild guild, SocketGuildUser author, SocketUser toBan, TimeSpan duration, - string reason) { - var guildBanMessage = $"({author}) {reason}"; - + public static async Task BanUser(CommandProcessor cmd, SocketUser toBan, TimeSpan duration, string reason) { + var author = cmd.Context.User; + var guild = cmd.Context.Guild; await Utils.SendDirectMessage(toBan, string.Format(Messages.YouWereBanned, author.Mention, guild.Name, Utils.Wrap(reason))); + var guildBanMessage = $"({author}) {reason}"; await guild.AddBanAsync(toBan, 0, guildBanMessage); var feedback = string.Format(Messages.FeedbackUserBanned, toBan.Mention, - Utils.GetHumanizedTimeOffset(ref duration), Utils.Wrap(reason)); - Success(feedback, author.Mention, false, false); - await Utils.SendFeedback(feedback, guild.Id, author.Mention, true); + Utils.GetHumanizedTimeOffset(duration), Utils.Wrap(reason)); + cmd.Reply(feedback, ":hammer: "); + cmd.Audit(feedback); if (duration.TotalSeconds > 0) { var _ = async () => { await Task.Delay(duration); - await UnbanCommand.UnbanUser(guild, guild.CurrentUser, toBan, Messages.PunishmentExpired); + await UnbanCommand.UnbanUser(cmd, toBan.Id, Messages.PunishmentExpired); }; } } -} +} \ No newline at end of file diff --git a/Boyfriend/Commands/ClearCommand.cs b/Boyfriend/Commands/ClearCommand.cs index 6b51075..164e0c6 100644 --- a/Boyfriend/Commands/ClearCommand.cs +++ b/Boyfriend/Commands/ClearCommand.cs @@ -1,46 +1,23 @@ using Discord; -using Discord.Commands; using Discord.WebSocket; namespace Boyfriend.Commands; public class ClearCommand : Command { public override string[] Aliases { get; } = { "clear", "purge", "очистить", "стереть" }; - public override int ArgsLengthRequired => 1; - public override async Task Run(SocketCommandContext context, string[] args) { - var user = (SocketGuildUser)context.User; + public override async Task Run(CommandProcessor cmd, string[] args) { + if (cmd.Context.Channel is not SocketTextChannel channel) throw new Exception(); - if (context.Channel is not SocketTextChannel channel) throw new Exception(); + if (!cmd.HasPermission(GuildPermission.ManageMessages)) return; - var permissionCheckResponse = CommandHandler.HasPermission(ref user, GuildPermission.ManageMessages); - if (permissionCheckResponse is not "") { - Error(permissionCheckResponse, true); - return; - } + var toDelete = cmd.GetNumberRange(args, 0, 1, 200, "ClearAmount"); + if (toDelete == null) return; + var messages = await channel.GetMessagesAsync((int)(toDelete + 1)).FlattenAsync(); - if (!int.TryParse(args[0], out var toDelete)) { - Error(Messages.ClearInvalidAmountSpecified, false); - return; - } + var user = (SocketGuildUser)cmd.Context.User; + await channel.DeleteMessagesAsync(messages, Utils.GetRequestOptions(user.ToString()!)); - switch (toDelete) { - case < 1: - Error(Messages.ClearNegativeAmount, false); - break; - case > 200: - Error(Messages.ClearAmountTooLarge, false); - break; - default: - var messages = await channel.GetMessagesAsync(toDelete + 1).FlattenAsync(); - - await channel.DeleteMessagesAsync(messages, Utils.GetRequestOptions(user.ToString()!)); - - await Utils.SendFeedback( - string.Format(Messages.FeedbackMessagesCleared, (toDelete + 1).ToString(), channel.Mention), - context.Guild.Id, user.Mention); - - break; - } + cmd.Audit(string.Format(Messages.FeedbackMessagesCleared, (toDelete + 1).ToString())); } } \ No newline at end of file diff --git a/Boyfriend/Commands/Command.cs b/Boyfriend/Commands/Command.cs index ec8590b..cf8563b 100644 --- a/Boyfriend/Commands/Command.cs +++ b/Boyfriend/Commands/Command.cs @@ -1,31 +1,7 @@ -using System.Text; -using Discord.Commands; - -namespace Boyfriend.Commands; +namespace Boyfriend.Commands; public abstract class Command { public abstract string[] Aliases { get; } - public abstract int ArgsLengthRequired { get; } - public abstract Task Run(SocketCommandContext context, string[] args); - - protected static void Output(ref StringBuilder message) { - CommandHandler.StackedReplyMessage.Append(message).AppendLine(); - } - - protected static void Success(string message, string userMention, bool sendPublicFeedback = false, - bool sendPrivateFeedback = true) { - CommandHandler.StackedReplyMessage.Append(":white_check_mark: ").AppendLine(message); - if (sendPrivateFeedback) - Utils.StackFeedback(ref message, ref userMention, sendPublicFeedback); - } - - protected static void Warn(string message) { - CommandHandler.StackedReplyMessage.Append(":warning: ").AppendLine(message); - } - - protected static void Error(string message, bool accessDenied) { - var symbol = accessDenied ? ":no_entry_sign: " : ":x: "; - CommandHandler.StackedReplyMessage.Append(symbol).AppendLine(message); - } + public abstract Task Run(CommandProcessor cmd, string[] args); } \ No newline at end of file diff --git a/Boyfriend/Commands/HelpCommand.cs b/Boyfriend/Commands/HelpCommand.cs index 656fa48..1a27bcd 100644 --- a/Boyfriend/Commands/HelpCommand.cs +++ b/Boyfriend/Commands/HelpCommand.cs @@ -1,20 +1,18 @@ -using Discord.Commands; -using Humanizer; +using Humanizer; namespace Boyfriend.Commands; public class HelpCommand : Command { - public override string[] Aliases { get; } = {"help", "помощь", "справка"}; - public override int ArgsLengthRequired => 0; + public override string[] Aliases { get; } = { "help", "помощь", "справка" }; - public override Task Run(SocketCommandContext context, string[] args) { - var prefix = Boyfriend.GetGuildConfig(context.Guild.Id)["Prefix"]; + public override Task Run(CommandProcessor cmd, string[] args) { + var prefix = Boyfriend.GetGuildConfig(cmd.Context.Guild.Id)["Prefix"]; var toSend = Boyfriend.StringBuilder.Append(Messages.CommandHelp); - foreach (var command in CommandHandler.Commands) + foreach (var command in CommandProcessor.Commands) toSend.Append( $"\n`{prefix}{command.Aliases[0]}`: {Utils.GetMessage($"CommandDescription{command.Aliases[0].Titleize()}")}"); - Output(ref toSend); + cmd.Reply(toSend.ToString(), ":page_facing_up: "); toSend.Clear(); return Task.CompletedTask; diff --git a/Boyfriend/Commands/KickCommand.cs b/Boyfriend/Commands/KickCommand.cs index fa1ab32..ba1a139 100644 --- a/Boyfriend/Commands/KickCommand.cs +++ b/Boyfriend/Commands/KickCommand.cs @@ -1,49 +1,31 @@ using Discord; -using Discord.Commands; using Discord.WebSocket; namespace Boyfriend.Commands; public class KickCommand : Command { public override string[] Aliases { get; } = { "kick", "кик", "выгнать" }; - public override int ArgsLengthRequired => 2; - public override async Task Run(SocketCommandContext context, string[] args) { - var author = (SocketGuildUser)context.User; + public override async Task Run(CommandProcessor cmd, string[] args) { + var toKick = cmd.GetMember(args, 0, "ToKick"); + if (toKick == null || !cmd.HasPermission(GuildPermission.KickMembers)) return; - var permissionCheckResponse = CommandHandler.HasPermission(ref author, GuildPermission.KickMembers); - if (permissionCheckResponse is not "") { - Error(permissionCheckResponse, true); - return; - } + if (!cmd.CanInteractWith(toKick, "Kick")) return; - var toKick = Utils.ParseMember(context.Guild, args[0]); - - if (toKick == null) { - Error(Messages.UserNotInGuild, false); - return; - } - - var interactionCheckResponse = CommandHandler.CanInteract(ref author, ref toKick); - if (interactionCheckResponse is not "") { - Error(interactionCheckResponse, true); - return; - } - - await KickMember(context.Guild, author, toKick, Utils.JoinString(ref args, 1)); - - Success( - string.Format(Messages.FeedbackMemberKicked, toKick.Mention, - Utils.Wrap(Utils.JoinString(ref args, 1))), author.Mention); + await KickMember(cmd, toKick, cmd.GetRemaining(args, 1, "KickReason")); } - private static async Task KickMember(IGuild guild, SocketUser author, SocketGuildUser toKick, string reason) { - var authorMention = author.Mention; - var guildKickMessage = $"({author}) {reason}"; + private static async Task KickMember(CommandProcessor cmd, SocketGuildUser toKick, string? reason) { + if (reason == null) return; + var guildKickMessage = $"({cmd.Context.User}) {reason}"; await Utils.SendDirectMessage(toKick, - string.Format(Messages.YouWereKicked, authorMention, guild.Name, Utils.Wrap(reason))); + string.Format(Messages.YouWereKicked, cmd.Context.User.Mention, cmd.Context.Guild.Name, + Utils.Wrap(reason))); await toKick.KickAsync(guildKickMessage); + var format = string.Format(Messages.FeedbackMemberKicked, toKick.Mention, Utils.Wrap(reason)); + cmd.Reply(format, ":police_car: "); + cmd.Audit(format); } } \ No newline at end of file diff --git a/Boyfriend/Commands/MuteCommand.cs b/Boyfriend/Commands/MuteCommand.cs index d9b61d7..c972614 100644 --- a/Boyfriend/Commands/MuteCommand.cs +++ b/Boyfriend/Commands/MuteCommand.cs @@ -1,5 +1,4 @@ using Discord; -using Discord.Commands; using Discord.Net; using Discord.WebSocket; @@ -7,72 +6,45 @@ namespace Boyfriend.Commands; public class MuteCommand : Command { public override string[] Aliases { get; } = { "mute", "timeout", "заглушить", "мут" }; - public override int ArgsLengthRequired => 2; - public override async Task Run(SocketCommandContext context, string[] args) { - var toMute = Utils.ParseMember(context.Guild, args[0]); - var reason = Utils.JoinString(ref args, 2); + public override async Task Run(CommandProcessor cmd, string[] args) { + var toMute = cmd.GetMember(args, 0, "ToMute"); + if (toMute == null) return; - var duration = Utils.GetTimeSpan(ref args[1]) ?? TimeSpan.FromMilliseconds(-1); - if (duration.TotalSeconds < 0) { - Warn(Messages.DurationParseFailed); - reason = Utils.JoinString(ref args, 1); - } - - - if (reason is "") { - Error(Messages.ReasonRequired, false); - return; - } - - if (toMute == null) { - Error(Messages.UserNotInGuild, false); - return; - } - - var guild = context.Guild; - var role = Utils.GetMuteRole(ref guild); + var duration = CommandProcessor.GetTimeSpan(args, 1); + var reason = cmd.GetRemaining(args, duration.TotalSeconds < 1 ? 1 : 2, "MuteReason"); + if (reason == null) return; + var role = Utils.GetMuteRole(cmd.Context.Guild); if (role != null) { if (toMute.Roles.Contains(role) || (toMute.TimedOutUntil != null && toMute.TimedOutUntil.Value.ToUnixTimeMilliseconds() > DateTimeOffset.Now.ToUnixTimeMilliseconds())) { - Error(Messages.MemberAlreadyMuted, false); + cmd.Reply(Messages.MemberAlreadyMuted, ":x: "); return; } } - var rolesRemoved = Boyfriend.GetRemovedRoles(context.Guild.Id); + var rolesRemoved = Boyfriend.GetRemovedRoles(cmd.Context.Guild.Id); if (rolesRemoved.ContainsKey(toMute.Id)) { foreach (var roleId in rolesRemoved[toMute.Id]) await toMute.AddRoleAsync(roleId); rolesRemoved.Remove(toMute.Id); - CommandHandler.ConfigWriteScheduled = true; - Warn(Messages.RolesReturned); + cmd.ConfigWriteScheduled = true; + cmd.Reply(Messages.RolesReturned, ":warning: "); } - var author = (SocketGuildUser)context.User; + if (!cmd.HasPermission(GuildPermission.ModerateMembers) || !cmd.CanInteractWith(toMute, "Mute")) return; - var permissionCheckResponse = CommandHandler.HasPermission(ref author, GuildPermission.ModerateMembers); - if (permissionCheckResponse is not "") { - Error(permissionCheckResponse, true); - return; - } - - var interactionCheckResponse = CommandHandler.CanInteract(ref author, ref toMute); - if (interactionCheckResponse is not "") { - Error(interactionCheckResponse, true); - return; - } - - await MuteMember(guild, author, toMute, duration, reason); + await MuteMember(cmd, toMute, duration, reason); } - private static async Task MuteMember(SocketGuild guild, SocketUser author, SocketGuildUser toMute, + private static async Task MuteMember(CommandProcessor cmd, SocketGuildUser toMute, TimeSpan duration, string reason) { + var guild = cmd.Context.Guild; var config = Boyfriend.GetGuildConfig(guild.Id); - var requestOptions = Utils.GetRequestOptions($"({author}) {reason}"); - var role = Utils.GetMuteRole(ref guild); + var requestOptions = Utils.GetRequestOptions($"({cmd.Context.User}) {reason}"); + var role = Utils.GetMuteRole(guild); var hasDuration = duration.TotalSeconds > 0; if (role != null) { @@ -84,30 +56,30 @@ public class MuteCommand : Command { await toMute.RemoveRoleAsync(role); rolesRemoved.Add(userRole.Id); } catch (HttpException e) { - Warn(string.Format(Messages.RoleRemovalFailed, $"<@&{userRole}>", Utils.Wrap(e.Reason))); + cmd.Reply(string.Format(Messages.RoleRemovalFailed, $"<@&{userRole}>", Utils.Wrap(e.Reason)), + ":warning: "); } Boyfriend.GetRemovedRoles(guild.Id).Add(toMute.Id, rolesRemoved.AsReadOnly()); - CommandHandler.ConfigWriteScheduled = true; - - if (hasDuration) { - var copy = duration; - var _ = async () => { - await Task.Delay(copy); - await UnmuteCommand.UnmuteMember(guild, guild.CurrentUser, toMute, Messages.PunishmentExpired); - }; - } + cmd.ConfigWriteScheduled = true; } await toMute.AddRoleAsync(role, requestOptions); + + if (hasDuration) { + var _ = async () => { + await Task.Delay(duration); + await UnmuteCommand.UnmuteMember(cmd, toMute, Messages.PunishmentExpired); + }; + } } else { if (!hasDuration || duration.TotalDays > 28) { - Error(Messages.DurationRequiredForTimeOuts, false); + cmd.Reply(Messages.DurationRequiredForTimeOuts, ":x: "); return; } if (toMute.IsBot) { - Error(Messages.CannotTimeOutBot, false); + cmd.Reply(Messages.CannotTimeOutBot, ":x: "); return; } @@ -115,9 +87,9 @@ public class MuteCommand : Command { } var feedback = string.Format(Messages.FeedbackMemberMuted, toMute.Mention, - Utils.GetHumanizedTimeOffset(ref duration), + Utils.GetHumanizedTimeOffset(duration), Utils.Wrap(reason)); - Success(feedback, author.Mention, false, false); - await Utils.SendFeedback(feedback, guild.Id, author.Mention, true); + cmd.Reply(feedback, ":mute: "); + cmd.Audit(feedback); } -} +} \ No newline at end of file diff --git a/Boyfriend/Commands/PingCommand.cs b/Boyfriend/Commands/PingCommand.cs index c5189e4..4034f96 100644 --- a/Boyfriend/Commands/PingCommand.cs +++ b/Boyfriend/Commands/PingCommand.cs @@ -1,17 +1,16 @@ -using Discord.Commands; - -namespace Boyfriend.Commands; +namespace Boyfriend.Commands; public class PingCommand : Command { - public override string[] Aliases { get; } = {"ping", "latency", "pong", "пинг", "задержка", "понг"}; - public override int ArgsLengthRequired => 0; + public override string[] Aliases { get; } = { "ping", "latency", "pong", "пинг", "задержка", "понг" }; - public override Task Run(SocketCommandContext context, string[] args) { + public override Task Run(CommandProcessor cmd, string[] args) { var builder = Boyfriend.StringBuilder; - builder.Append(Utils.GetBeep()).Append(Boyfriend.Client.Latency).Append(Messages.Milliseconds); + builder.Append(Utils.GetBeep()) + .Append(Math.Abs(DateTimeOffset.Now.Subtract(cmd.Context.Message.Timestamp).TotalMilliseconds)) + .Append(Messages.Milliseconds); - Output(ref builder); + cmd.Reply(builder.ToString(), ":signal_strength: "); builder.Clear(); return Task.CompletedTask; diff --git a/Boyfriend/Commands/SettingsCommand.cs b/Boyfriend/Commands/SettingsCommand.cs index 0a7fabc..0a5f3cf 100644 --- a/Boyfriend/Commands/SettingsCommand.cs +++ b/Boyfriend/Commands/SettingsCommand.cs @@ -1,23 +1,14 @@ using Discord; -using Discord.Commands; -using Discord.WebSocket; namespace Boyfriend.Commands; public class SettingsCommand : Command { public override string[] Aliases { get; } = { "settings", "config", "настройки", "конфиг" }; - public override int ArgsLengthRequired => 0; - public override Task Run(SocketCommandContext context, string[] args) { - var author = (SocketGuildUser)context.User; + public override Task Run(CommandProcessor cmd, string[] args) { + if (!cmd.HasPermission(GuildPermission.ManageGuild)) return Task.CompletedTask; - var permissionCheckResponse = CommandHandler.HasPermission(ref author, GuildPermission.ManageGuild); - if (permissionCheckResponse is not "") { - Error(permissionCheckResponse, true); - return Task.CompletedTask; - } - - var guild = context.Guild; + var guild = cmd.Context.Guild; var config = Boyfriend.GetGuildConfig(guild.Id); if (args.Length == 0) { @@ -48,7 +39,7 @@ public class SettingsCommand : Command { .AppendFormat(format, currentValue).AppendLine(); } - Output(ref currentSettings); + cmd.Reply(currentSettings.ToString(), ":gear: "); currentSettings.Clear(); return Task.CompletedTask; } @@ -66,19 +57,20 @@ public class SettingsCommand : Command { } if (!exists) { - Error(Messages.SettingDoesntExist, false); + cmd.Reply(Messages.SettingDoesntExist, ":x: "); return Task.CompletedTask; } - string value; + string? value; if (args.Length >= 2) { - value = Utils.JoinString(ref args, 1); + value = cmd.GetRemaining(args, 1, "Setting"); + if (value == null) return Task.CompletedTask; if (selectedSetting is "EventStartedReceivers") { value = value.Replace(" ", "").ToLower(); if (value.StartsWith(",") || value.Count(x => x == ',') > 1 || (!value.Contains("interested") && !value.Contains("role"))) { - Error(Messages.InvalidSettingValue, false); + cmd.Reply(Messages.InvalidSettingValue, ":x: "); return Task.CompletedTask; } } @@ -91,7 +83,7 @@ public class SettingsCommand : Command { _ => value }; if (!IsBool(value)) { - Error(Messages.InvalidSettingValue, false); + cmd.Reply(Messages.InvalidSettingValue, ":x: "); return Task.CompletedTask; } } @@ -124,22 +116,23 @@ public class SettingsCommand : Command { config[selectedSetting] = Boyfriend.DefaultConfig[selectedSetting]; } else { if (value == config[selectedSetting]) { - Error(string.Format(Messages.SettingsNothingChanged, localizedSelectedSetting, formattedValue), false); + cmd.Reply(string.Format(Messages.SettingsNothingChanged, localizedSelectedSetting, formattedValue), + ":x: "); return Task.CompletedTask; } if (selectedSetting is "Lang" && value is not "ru" and not "en") { - Error(Messages.LanguageNotSupported, false); + cmd.Reply(Messages.LanguageNotSupported, ":x: "); return Task.CompletedTask; } if (selectedSetting.EndsWith("Channel") && guild.GetTextChannel(mention) == null) { - Error(Messages.InvalidChannel, false); + cmd.Reply(Messages.InvalidChannel, ":x: "); return Task.CompletedTask; } if (selectedSetting.EndsWith("Role") && guild.GetRole(mention) == null) { - Error(Messages.InvalidRole, false); + cmd.Reply(Messages.InvalidRole, ":x: "); return Task.CompletedTask; } @@ -153,10 +146,11 @@ public class SettingsCommand : Command { localizedSelectedSetting = Utils.GetMessage($"Settings{selectedSetting}"); } - CommandHandler.ConfigWriteScheduled = true; + cmd.ConfigWriteScheduled = true; - Success(string.Format(Messages.FeedbackSettingsUpdated, localizedSelectedSetting, formattedValue), - author.Mention); + var replyFormat = string.Format(Messages.FeedbackSettingsUpdated, localizedSelectedSetting, formattedValue); + cmd.Reply(replyFormat, ":control_knobs: "); + cmd.Audit(replyFormat, false); return Task.CompletedTask; } @@ -167,4 +161,4 @@ public class SettingsCommand : Command { private static bool IsBool(string value) { return value is "true" or "false"; } -} +} \ No newline at end of file diff --git a/Boyfriend/Commands/UnbanCommand.cs b/Boyfriend/Commands/UnbanCommand.cs index ac2edef..602f497 100644 --- a/Boyfriend/Commands/UnbanCommand.cs +++ b/Boyfriend/Commands/UnbanCommand.cs @@ -1,45 +1,27 @@ using Discord; -using Discord.Commands; -using Discord.WebSocket; namespace Boyfriend.Commands; public class UnbanCommand : Command { public override string[] Aliases { get; } = { "unban", "разбан" }; - public override int ArgsLengthRequired => 2; - public override async Task Run(SocketCommandContext context, string[] args) { - var author = (SocketGuildUser)context.User; + public override async Task Run(CommandProcessor cmd, string[] args) { + if (!cmd.HasPermission(GuildPermission.BanMembers)) return; - var permissionCheckResponse = CommandHandler.HasPermission(ref author, GuildPermission.BanMembers); - if (permissionCheckResponse is not "") { - Error(permissionCheckResponse, true); - return; - } + var id = cmd.GetBan(args, 0); + if (id == null) return; + var reason = cmd.GetRemaining(args, 1, "UnbanReason"); + if (reason == null) return; - var toUnban = Utils.ParseUser(args[0]); - - if (toUnban == null) { - Error(Messages.UserDoesntExist, false); - return; - } - - var reason = Utils.JoinString(ref args, 1); - - await UnbanUser(context.Guild, author, toUnban, reason); + await UnbanUser(cmd, id.Value, reason); } - public static async Task UnbanUser(SocketGuild guild, SocketGuildUser author, SocketUser toUnban, string reason) { - if (guild.GetBanAsync(toUnban.Id) == null) { - Error(Messages.UserNotBanned, false); - return; - } + public static async Task UnbanUser(CommandProcessor cmd, ulong id, string reason) { + var requestOptions = Utils.GetRequestOptions($"({cmd.Context.User}) {reason}"); + await cmd.Context.Guild.RemoveBanAsync(id, requestOptions); - var requestOptions = Utils.GetRequestOptions($"({author}) {reason}"); - await guild.RemoveBanAsync(toUnban, requestOptions); - - var feedback = string.Format(Messages.FeedbackUserUnbanned, toUnban.Mention, Utils.Wrap(reason)); - Success(feedback, author.Mention, false, false); - await Utils.SendFeedback(feedback, guild.Id, author.Mention, true); + var feedback = string.Format(Messages.FeedbackUserUnbanned, $"<@{id.ToString()}>", Utils.Wrap(reason)); + cmd.Reply(feedback); + cmd.Audit(feedback); } } \ No newline at end of file diff --git a/Boyfriend/Commands/UnmuteCommand.cs b/Boyfriend/Commands/UnmuteCommand.cs index a5e8d76..f5de6ed 100644 --- a/Boyfriend/Commands/UnmuteCommand.cs +++ b/Boyfriend/Commands/UnmuteCommand.cs @@ -1,59 +1,39 @@ using Discord; -using Discord.Commands; using Discord.WebSocket; namespace Boyfriend.Commands; public class UnmuteCommand : Command { public override string[] Aliases { get; } = { "unmute", "размут" }; - public override int ArgsLengthRequired => 2; - public override async Task Run(SocketCommandContext context, string[] args) { - var author = (SocketGuildUser)context.User; + public override async Task Run(CommandProcessor cmd, string[] args) { + if (!cmd.HasPermission(GuildPermission.ModerateMembers)) return; - var permissionCheckResponse = CommandHandler.HasPermission(ref author, GuildPermission.ModerateMembers); - if (permissionCheckResponse is not "") { - Error(permissionCheckResponse, true); - return; - } - - var toUnmute = Utils.ParseMember(context.Guild, args[0]); - - if (toUnmute == null) { - Error(Messages.UserDoesntExist, false); - return; - } - - var interactionCheckResponse = CommandHandler.CanInteract(ref author, ref toUnmute); - if (interactionCheckResponse is not "") { - Error(interactionCheckResponse, true); - return; - } - - var reason = Utils.JoinString(ref args, 1); - await UnmuteMember(context.Guild, author, toUnmute, reason); + var toUnmute = cmd.GetMember(args, 0, "ToUnmute"); + var reason = cmd.GetRemaining(args, 1, "UnmuteReason"); + if (toUnmute == null || reason == null || !cmd.CanInteractWith(toUnmute, "Unmute")) return; + await UnmuteMember(cmd, toUnmute, reason); } - public static async Task UnmuteMember(SocketGuild guild, SocketGuildUser author, SocketGuildUser toUnmute, + public static async Task UnmuteMember(CommandProcessor cmd, SocketGuildUser toUnmute, string reason) { - var requestOptions = Utils.GetRequestOptions($"({author}) {reason}"); - var role = Utils.GetMuteRole(ref guild); + var requestOptions = Utils.GetRequestOptions($"({cmd.Context.User}) {reason}"); + var role = Utils.GetMuteRole(cmd.Context.Guild); if (role != null && toUnmute.Roles.Contains(role)) { - var rolesRemoved = Boyfriend.GetRemovedRoles(guild.Id); + var rolesRemoved = Boyfriend.GetRemovedRoles(cmd.Context.Guild.Id); if (rolesRemoved.ContainsKey(toUnmute.Id)) { await toUnmute.AddRolesAsync(rolesRemoved[toUnmute.Id]); rolesRemoved.Remove(toUnmute.Id); - CommandHandler.ConfigWriteScheduled = true; + cmd.ConfigWriteScheduled = true; } await toUnmute.RemoveRoleAsync(role, requestOptions); - } - else { + } else { if (toUnmute.TimedOutUntil == null || toUnmute.TimedOutUntil.Value.ToUnixTimeMilliseconds() < DateTimeOffset.Now.ToUnixTimeMilliseconds()) { - Error(Messages.MemberNotMuted, false); + cmd.Reply(Messages.MemberNotMuted, ":x: "); return; } @@ -61,7 +41,7 @@ public class UnmuteCommand : Command { } var feedback = string.Format(Messages.FeedbackMemberUnmuted, toUnmute.Mention, Utils.Wrap(reason)); - Success(feedback, author.Mention, false, false); - await Utils.SendFeedback(feedback, guild.Id, author.Mention, true); + cmd.Reply(feedback, ":loud_sound: "); + cmd.Audit(feedback); } } \ No newline at end of file diff --git a/Boyfriend/EventHandler.cs b/Boyfriend/EventHandler.cs index c4f6a99..94e75ac 100644 --- a/Boyfriend/EventHandler.cs +++ b/Boyfriend/EventHandler.cs @@ -64,7 +64,7 @@ public class EventHandler { if ((message.MentionedUsers.Count > 3 || message.MentionedRoles.Count > 2) && !user.GuildPermissions.MentionEveryone) { - await BanCommand.BanUser(guild, guild.CurrentUser, user, TimeSpan.FromMilliseconds(-1), + await BanCommand.BanUser(new CommandProcessor(message), user, TimeSpan.FromMilliseconds(-1), Messages.AutobanReason); return; } @@ -83,7 +83,7 @@ public class EventHandler { (message.Content.Contains(prev) || message.Content.Contains(prevFailsafe)))) return; - _ = CommandHandler.HandleCommand(message); + _ = new CommandProcessor(message).HandleCommand(); } private static async Task MessageUpdatedEvent(Cacheable messageCached, SocketMessage messageSocket, @@ -176,4 +176,4 @@ public class EventHandler { await channel.SendMessageAsync(string.Format(Messages.EventCompleted, Utils.Wrap(scheduledEvent.Name), Utils.Wrap(scheduledEvent.StartTime.Subtract(DateTimeOffset.Now).Negate().ToString()))); } -} +} \ No newline at end of file diff --git a/Boyfriend/Messages.Designer.cs b/Boyfriend/Messages.Designer.cs index 3b588bf..ad39dc1 100644 --- a/Boyfriend/Messages.Designer.cs +++ b/Boyfriend/Messages.Designer.cs @@ -95,6 +95,87 @@ namespace Boyfriend { } } + /// + /// Looks up a localized string similar to I cannot ban users from this guild!. + /// + internal static string BotCannotBanMembers { + get { + return ResourceManager.GetString("BotCannotBanMembers", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to I cannot ban this user!. + /// + internal static string BotCannotBanTarget { + get { + return ResourceManager.GetString("BotCannotBanTarget", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to I cannot kick members from this guild!. + /// + internal static string BotCannotKickMembers { + get { + return ResourceManager.GetString("BotCannotKickMembers", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to I cannot kick this member!. + /// + internal static string BotCannotKickTarget { + get { + return ResourceManager.GetString("BotCannotKickTarget", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to I cannot manage this guild!. + /// + internal static string BotCannotManageGuild { + get { + return ResourceManager.GetString("BotCannotManageGuild", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to I cannot manage messages in this guild!. + /// + internal static string BotCannotManageMessages { + get { + return ResourceManager.GetString("BotCannotManageMessages", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to I cannot moderate members in this guild!. + /// + internal static string BotCannotModerateMembers { + get { + return ResourceManager.GetString("BotCannotModerateMembers", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to I cannot mute this member!. + /// + internal static string BotCannotMuteTarget { + get { + return ResourceManager.GetString("BotCannotMuteTarget", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to I cannot unmute this member!. + /// + internal static string BotCannotUnmuteTarget { + get { + return ResourceManager.GetString("BotCannotUnmuteTarget", resourceCulture); + } + } + /// /// Looks up a localized string similar to Deleted message from {0} in channel {1}: {2}. /// @@ -132,7 +213,16 @@ namespace Boyfriend { } /// - /// Looks up a localized string similar to Too many messages specified!. + /// Looks up a localized string similar to You need to specify an integer from {0} to {1} instead of {2}!. + /// + internal static string ClearAmountInvalid { + get { + return ResourceManager.GetString("ClearAmountInvalid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You specified more than {0} messages!. /// internal static string ClearAmountTooLarge { get { @@ -141,20 +231,11 @@ namespace Boyfriend { } /// - /// Looks up a localized string similar to Invalid message amount specified!. + /// Looks up a localized string similar to You specified less than {0} messages!. /// - internal static string ClearInvalidAmountSpecified { + internal static string ClearAmountTooSmall { get { - return ResourceManager.GetString("ClearInvalidAmountSpecified", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Negative message amount specified!. - /// - internal static string ClearNegativeAmount { - get { - return ResourceManager.GetString("ClearNegativeAmount", resourceCulture); + return ResourceManager.GetString("ClearAmountTooSmall", resourceCulture); } } @@ -266,15 +347,6 @@ namespace Boyfriend { } } - /// - /// Looks up a localized string similar to Couldn't find guild by message!. - /// - internal static string CouldntFindGuildByChannel { - get { - return ResourceManager.GetString("CouldntFindGuildByChannel", resourceCulture); - } - } - /// /// Looks up a localized string similar to Current settings:. /// @@ -293,15 +365,6 @@ namespace Boyfriend { } } - /// - /// Looks up a localized string similar to I couldn't parse the specified duration! One of the components could be outside it's valid range (e.g. `24h` or `60m`). - /// - internal static string DurationParseFailed { - get { - return ResourceManager.GetString("DurationParseFailed", resourceCulture); - } - } - /// /// Looks up a localized string similar to I cannot mute someone for more than 28 days using timeouts! Either specify a duration shorter than 28 days, or set a mute role in settings. /// @@ -428,60 +491,6 @@ namespace Boyfriend { } } - /// - /// Looks up a localized string similar to Members are in different guilds!. - /// - internal static string InteractionsDifferentGuilds { - get { - return ResourceManager.GetString("InteractionsDifferentGuilds", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to I cannot interact with this member!. - /// - internal static string InteractionsFailedBot { - get { - return ResourceManager.GetString("InteractionsFailedBot", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You cannot interact with this member!. - /// - internal static string InteractionsFailedUser { - get { - return ResourceManager.GetString("InteractionsFailedUser", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You cannot interact with me!. - /// - internal static string InteractionsMe { - get { - return ResourceManager.GetString("InteractionsMe", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You cannot interact with guild owner!. - /// - internal static string InteractionsOwner { - get { - return ResourceManager.GetString("InteractionsOwner", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You cannot interact with yourself!. - /// - internal static string InteractionsYourself { - get { - return ResourceManager.GetString("InteractionsYourself", resourceCulture); - } - } - /// /// Looks up a localized string similar to This channel does not exist!. /// @@ -491,6 +500,15 @@ namespace Boyfriend { } } + /// + /// Looks up a localized string similar to You need to specify a guild member instead of {0}!. + /// + internal static string InvalidMember { + get { + return ResourceManager.GetString("InvalidMember", resourceCulture); + } + } + /// /// Looks up a localized string similar to This role does not exist!. /// @@ -509,6 +527,15 @@ namespace Boyfriend { } } + /// + /// Looks up a localized string similar to You need to specify a user instead of {0}!. + /// + internal static string InvalidUser { + get { + return ResourceManager.GetString("InvalidUser", resourceCulture); + } + } + /// /// Looks up a localized string similar to Language not supported!. /// @@ -536,15 +563,6 @@ namespace Boyfriend { } } - /// - /// Looks up a localized string similar to {0} unmuted {1} for {2}. - /// - internal static string MemberUnmuted { - get { - return ResourceManager.GetString("MemberUnmuted", resourceCulture); - } - } - /// /// Looks up a localized string similar to ms. /// @@ -554,6 +572,87 @@ namespace Boyfriend { } } + /// + /// Looks up a localized string similar to You need to specify a reason to ban this user!. + /// + internal static string MissingBanReason { + get { + return ResourceManager.GetString("MissingBanReason", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You need to specify a reason to kick this member!. + /// + internal static string MissingKickReason { + get { + return ResourceManager.GetString("MissingKickReason", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You need to specify a guild member!. + /// + internal static string MissingMember { + get { + return ResourceManager.GetString("MissingMember", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You need to specify a reason to mute this member!. + /// + internal static string MissingMuteReason { + get { + return ResourceManager.GetString("MissingMuteReason", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You need to specify an integer from {0} to {1}!. + /// + internal static string MissingNumber { + get { + return ResourceManager.GetString("MissingNumber", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You need to specify a setting to change!. + /// + internal static string MissingSetting { + get { + return ResourceManager.GetString("MissingSetting", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You need to specify a reason to unban this user!. + /// + internal static string MissingUnbanReason { + get { + return ResourceManager.GetString("MissingUnbanReason", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You need to specify a reason for unmute this member!. + /// + internal static string MissingUnmuteReason { + get { + return ResourceManager.GetString("MissingUnmuteReason", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You need to specify a user!. + /// + internal static string MissingUser { + get { + return ResourceManager.GetString("MissingUser", resourceCulture); + } + } + /// /// Looks up a localized string similar to No. /// @@ -563,15 +662,6 @@ namespace Boyfriend { } } - /// - /// Looks up a localized string similar to Not enough arguments! Needed: {0}, provided: {1}. - /// - internal static string NotEnoughArguments { - get { - return ResourceManager.GetString("NotEnoughArguments", resourceCulture); - } - } - /// /// Looks up a localized string similar to Punishment expired. /// @@ -590,15 +680,6 @@ namespace Boyfriend { } } - /// - /// Looks up a localized string similar to You must specify a reason!. - /// - internal static string ReasonRequired { - get { - return ResourceManager.GetString("ReasonRequired", resourceCulture); - } - } - /// /// Looks up a localized string similar to Not specified. /// @@ -807,34 +888,196 @@ namespace Boyfriend { } /// - /// Looks up a localized string similar to Message deleted in {0}, but I forgot what was there. + /// Looks up a localized string similar to You cannot ban me!. /// - internal static string UncachedMessageDeleted { + internal static string UserCannotBanBot { get { - return ResourceManager.GetString("UncachedMessageDeleted", resourceCulture); + return ResourceManager.GetString("UserCannotBanBot", resourceCulture); } } /// - /// Looks up a localized string similar to Message edited from {0} in channel {1}, but I forgot what was there before the edit: {2}. + /// Looks up a localized string similar to You cannot ban users from this guild!. /// - internal static string UncachedMessageEdited { + internal static string UserCannotBanMembers { get { - return ResourceManager.GetString("UncachedMessageEdited", resourceCulture); + return ResourceManager.GetString("UserCannotBanMembers", resourceCulture); } } /// - /// Looks up a localized string similar to That user doesn't exist!. + /// Looks up a localized string similar to You cannot ban the owner of this guild!. /// - internal static string UserDoesntExist { + internal static string UserCannotBanOwner { get { - return ResourceManager.GetString("UserDoesntExist", resourceCulture); + return ResourceManager.GetString("UserCannotBanOwner", resourceCulture); } } /// - /// Looks up a localized string similar to User not banned!. + /// Looks up a localized string similar to You cannot ban this user!. + /// + internal static string UserCannotBanTarget { + get { + return ResourceManager.GetString("UserCannotBanTarget", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You cannot ban yourself!. + /// + internal static string UserCannotBanThemselves { + get { + return ResourceManager.GetString("UserCannotBanThemselves", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You cannot kick me!. + /// + internal static string UserCannotKickBot { + get { + return ResourceManager.GetString("UserCannotKickBot", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You cannot kick members from this guild!. + /// + internal static string UserCannotKickMembers { + get { + return ResourceManager.GetString("UserCannotKickMembers", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You cannot kick the owner of this guild!. + /// + internal static string UserCannotKickOwner { + get { + return ResourceManager.GetString("UserCannotKickOwner", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You cannot kick this member!. + /// + internal static string UserCannotKickTarget { + get { + return ResourceManager.GetString("UserCannotKickTarget", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You cannot kick yourself!. + /// + internal static string UserCannotKickThemselves { + get { + return ResourceManager.GetString("UserCannotKickThemselves", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You cannot manage this guild!. + /// + internal static string UserCannotManageGuild { + get { + return ResourceManager.GetString("UserCannotManageGuild", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You cannot manage messages in this guild!. + /// + internal static string UserCannotManageMessages { + get { + return ResourceManager.GetString("UserCannotManageMessages", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You cannot moderate members in this guild!. + /// + internal static string UserCannotModerateMembers { + get { + return ResourceManager.GetString("UserCannotModerateMembers", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You cannot mute me!. + /// + internal static string UserCannotMuteBot { + get { + return ResourceManager.GetString("UserCannotMuteBot", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You cannot mute the owner of this guild!. + /// + internal static string UserCannotMuteOwner { + get { + return ResourceManager.GetString("UserCannotMuteOwner", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You cannot mute this member!. + /// + internal static string UserCannotMuteTarget { + get { + return ResourceManager.GetString("UserCannotMuteTarget", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You cannot mute yourself!. + /// + internal static string UserCannotMuteThemselves { + get { + return ResourceManager.GetString("UserCannotMuteThemselves", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to .... + /// + internal static string UserCannotUnmuteBot { + get { + return ResourceManager.GetString("UserCannotUnmuteBot", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You don't need to unmute the owner of this guild!. + /// + internal static string UserCannotUnmuteOwner { + get { + return ResourceManager.GetString("UserCannotUnmuteOwner", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You cannot unmute this user!. + /// + internal static string UserCannotUnmuteTarget { + get { + return ResourceManager.GetString("UserCannotUnmuteTarget", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You are muted!. + /// + internal static string UserCannotUnmuteThemselves { + get { + return ResourceManager.GetString("UserCannotUnmuteThemselves", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This user is not banned!. /// internal static string UserNotBanned { get { @@ -851,15 +1094,6 @@ namespace Boyfriend { } } - /// - /// Looks up a localized string similar to {0} unbanned {1} for {2}. - /// - internal static string UserUnbanned { - get { - return ResourceManager.GetString("UserUnbanned", resourceCulture); - } - } - /// /// Looks up a localized string similar to Yes. /// diff --git a/Boyfriend/Messages.resx b/Boyfriend/Messages.resx index 4d1685d..88ab26c 100644 --- a/Boyfriend/Messages.resx +++ b/Boyfriend/Messages.resx @@ -24,24 +24,15 @@ PublicKeyToken=b77a5c561934e089 - - Couldn't find guild by message! - {0}I'm ready! (C#) - - Message deleted in {0}, but I forgot what was there - Deleted message from {0} in channel {1}: {2} Too many mentions in 1 message - - Message edited from {0} in channel {1}, but I forgot what was there before the edit: {2} - Edited message in channel {0}: {1} -> {2} @@ -63,35 +54,17 @@ You do not have permission to execute this command! - - Members are in different guilds! - - - You cannot interact with guild owner! - - - You cannot interact with yourself! - - - You cannot interact with me! - - - You cannot interact with this member! - - - I cannot interact with this member! - You were banned by {0} in guild {1} for {2} Punishment expired - - Negative message amount specified! + + You specified less than {0} messages! - Too many messages specified! + You specified more than {0} messages! Command help: @@ -148,7 +121,7 @@ No - User not banned! + This user is not banned! Member not muted! @@ -156,20 +129,11 @@ Someone removed the mute role manually! I added back all roles that I removed during the mute - - {0} unmuted {1} for {2} - - - {0} unbanned {1} for {2} - Welcome message - - Not enough arguments! Needed: {0}, provided: {1} - - - Invalid message amount specified! + + You need to specify an integer from {0} to {1} instead of {2}! Banned {0} for{1}: {2} @@ -192,9 +156,6 @@ This channel does not exist! - - I couldn't parse the specified duration! One of the components could be outside it's valid range (e.g. `24h` or `60m`) - I couldn't remove role {0} because of an error! {1} @@ -237,9 +198,6 @@ Event {0} has completed! Duration: {1} - - That user doesn't exist! - *[{0}: {1}]* @@ -297,7 +255,127 @@ Unmutes a member - - You must specify a reason! + + You need to specify an integer from {0} to {1}! + + + You need to specify a user! + + + You need to specify a user instead of {0}! + + + You need to specify a guild member! + + + You need to specify a guild member instead of {0}! + + + You cannot ban users from this guild! + + + You cannot manage messages in this guild! + + + You cannot kick members from this guild! + + + You cannot moderate members in this guild! + + + You cannot manage this guild! + + + I cannot ban users from this guild! + + + I cannot manage messages in this guild! + + + I cannot kick members from this guild! + + + I cannot moderate members in this guild! + + + I cannot manage this guild! + + + You need to specify a reason to ban this user! + + + You need to specify a reason to kick this member! + + + You need to specify a reason to mute this member! + + + You need to specify a reason to unban this user! + + + You need to specify a reason for unmute this member! + + + You need to specify a setting to change! + + + You cannot ban the owner of this guild! + + + You cannot ban yourself! + + + You cannot ban me! + + + I cannot ban this user! + + + You cannot ban this user! + + + You cannot kick the owner of this guild! + + + You cannot kick yourself! + + + You cannot kick me! + + + I cannot kick this member! + + + You cannot kick this member! + + + You cannot mute the owner of this guild! + + + You cannot mute yourself! + + + You cannot mute me! + + + I cannot mute this member! + + + You cannot mute this member! + + + You don't need to unmute the owner of this guild! + + + You are muted! + + + ... + + + I cannot unmute this member! + + + You cannot unmute this user! diff --git a/Boyfriend/Messages.ru.resx b/Boyfriend/Messages.ru.resx index 0c10ab5..78359d6 100644 --- a/Boyfriend/Messages.ru.resx +++ b/Boyfriend/Messages.ru.resx @@ -15,24 +15,15 @@ PublicKeyToken=b77a5c561934e089 - - Не удалось найти сервер по каналу! - {0}Я запустился! (C#) - - Удалено сообщение в канале {0}, но я забыл что там было - Удалено сообщение от {0} в канале {1}: {2} Слишком много упоминаний в одном сообщении - - Отредактировано сообщение от {0} в канале {1}, но я забыл что там было до редактирования: {2} - Отредактировано сообщение в канале {0}: {1} -> {2} @@ -54,35 +45,17 @@ У тебя недостаточно прав для выполнения этой команды! - - Участники находятся в разных гильдиях! - - - Ты не можешь взаимодействовать с владельцем сервера! - - - Ты не можешь взаимодействовать с самим собой! - - - Ты не можешь со мной взаимодействовать! - - - Ты не можешь взаимодействовать с этим участником! - - - Я не могу взаимодействовать с этим участником! - Тебя забанил {0} на сервере {1} за {2} Время наказания истекло - - Указано отрицательное количество сообщений! + + Указано менее {0} сообщений! - Указано слишком много сообщений! + Указано более {0} сообщений! Справка по командам: @@ -136,7 +109,7 @@ Нет - Пользователь не забанен! + Этот пользователь не забанен! Участник не заглушен! @@ -144,20 +117,11 @@ Кто-то убрал роль мута самостоятельно! Я вернул все роли, которые забрал при муте - - {0} возвращает из мута {1} за {2} - - - {0} возвращает из бана {1} за {2} - Приветствие - - Недостаточно аргументов! Требуется: {0}, указано: {1} - - - Указано неверное количество сообщений! + + Надо указать целое число от {0} до {1} вместо {2}! Забанен {0} на{1}: {2} @@ -180,9 +144,6 @@ Этот канал не существует! - - Мне не удалось обработать продолжительность! Один из компонентов может быть за пределами допустимого диапазона (например, `24ч` или `60м`) - Я не смог забрать роль {0} в связи с ошибкой! {1} @@ -228,9 +189,6 @@ Событие {0} завершено! Продолжительность: {1} - - Такого пользователя не существует! - *[{0}: {1}]* @@ -288,7 +246,127 @@ Разглушает участника - - Требуется указать причину! + + Надо указать целое число от {0} до {1}! + + + Надо указать пользователя! + + + Надо указать пользователя вместо {0}! + + + Надо указать участника сервера! + + + Надо указать участника сервера вместо {0}! + + + Ты не можешь банить пользователей на этом сервере! + + + Ты не можешь управлять сообщениями этого сервера! + + + Ты не можешь выгонять участников с этого сервера! + + + Ты не можешь модерировать участников этого сервера! + + + Ты не можешь настраивать этот сервер! + + + Я не могу банить пользователей на этом сервере! + + + Я не могу управлять сообщениями этого сервера! + + + Я не могу выгонять участников с этого сервера! + + + Я не могу модерировать участников этого сервера! + + + Я не могу настраивать этот сервер! + + + Надо указать причину для бана этого участника! + + + Надо указать причину для кика этого участника! + + + Надо указать причину для мута этого участника! + + + Надо указать настройку, которую нужно изменить! + + + Надо указать причину для разбана этого пользователя! + + + Надо указать причину для размута этого участника! + + + Ты не можешь меня забанить! + + + Ты не можешь забанить владельца этого сервера! + + + Ты не можешь забанить этого участника! + + + Ты не можешь себя забанить! + + + Я не могу забанить этого пользователя! + + + Ты не можешь выгнать владельца этого сервера! + + + Ты не можешь себя выгнать! + + + Ты не можешь меня выгнать! + + + Я не могу выгнать этого участника + + + Ты не можешь выгнать этого участника! + + + Ты не можешь заглушить владельца этого сервера! + + + Ты не можешь себя заглушить! + + + Ты не можешь заглушить меня! + + + Я не могу заглушить этого пользователя! + + + Ты не можешь заглушить этого участника! + + + Тебе не надо возвращать из мута владельца этого сервера! + + + Ты заглушен! + + + ... + + + Ты не можешь вернуть из мута этого пользователя! + + + Я не могу вернуть из мута этого пользователя! diff --git a/Boyfriend/Utils.cs b/Boyfriend/Utils.cs index 213196e..3802ceb 100644 --- a/Boyfriend/Utils.cs +++ b/Boyfriend/Utils.cs @@ -1,5 +1,6 @@ using System.Globalization; using System.Reflection; +using System.Text; using System.Text.RegularExpressions; using Discord; using Discord.Net; @@ -65,7 +66,7 @@ public static class Utils { } } - public static SocketRole? GetMuteRole(ref SocketGuild guild) { + public static SocketRole? GetMuteRole(SocketGuild guild) { var id = ulong.Parse(Boyfriend.GetGuildConfig(guild.Id)["MuteRole"]); if (MuteRoleCache.ContainsKey(id)) return MuteRoleCache[id]; SocketRole? role = null; @@ -90,41 +91,6 @@ public static class Utils { await channel.SendMessageAsync(text, false, null, null, allowRoles ? AllowRoles : AllowedMentions.None); } - public static TimeSpan? GetTimeSpan(ref string from) { - var chars = from.AsSpan(); - var numberBuilder = Boyfriend.StringBuilder; - int days = 0, hours = 0, minutes = 0, seconds = 0; - foreach (var c in chars) - if (char.IsDigit(c)) { numberBuilder.Append(c); } else { - if (numberBuilder.Length == 0) return null; - switch (c) { - case 'd' or 'D' or 'д' or 'Д': - days += int.Parse(numberBuilder.ToString()); - numberBuilder.Clear(); - break; - case 'h' or 'H' or 'ч' or 'Ч': - hours += int.Parse(numberBuilder.ToString()); - numberBuilder.Clear(); - break; - case 'm' or 'M' or 'м' or 'М': - minutes += int.Parse(numberBuilder.ToString()); - numberBuilder.Clear(); - break; - case 's' or 'S' or 'с' or 'С': - seconds += int.Parse(numberBuilder.ToString()); - numberBuilder.Clear(); - break; - default: return null; - } - } - - numberBuilder.Clear(); - return new TimeSpan(days, hours, minutes, seconds); - } - - public static string JoinString(ref string[] args, int startIndex) { - return string.Join(" ", args, startIndex, args.Length - startIndex); - } public static RequestOptions GetRequestOptions(string reason) { var options = RequestOptions.Default; @@ -139,7 +105,12 @@ public static class Utils { var toReturn = typeof(Messages).GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Static)?.GetValue(null) - ?.ToString()! ?? throw new Exception($"Could not find localized property: {propertyName}"); + ?.ToString(); + if (toReturn == null) { + Console.WriteLine($@"Could not find localized property: {propertyName}"); + return name; + } + ReflectionMessageCache.Add(name, toReturn); return toReturn; } @@ -154,13 +125,7 @@ public static class Utils { await SilentSendAsync(systemChannel, toSend); } - public static void StackFeedback(ref string feedback, ref string mention, bool isPublic) { - var toAppend = string.Format(Messages.FeedbackFormat, mention, feedback); - CommandHandler.StackedPrivateFeedback.AppendLine(toAppend); - if (isPublic) CommandHandler.StackedPublicFeedback.AppendLine(toAppend); - } - - public static string GetHumanizedTimeOffset(ref TimeSpan span) { + public static string GetHumanizedTimeOffset(TimeSpan span) { return span.TotalSeconds > 0 ? $" {span.Humanize(2, minUnit: TimeUnit.Second, maxUnit: TimeUnit.Month, culture: Messages.Culture)}" : Messages.Ever; @@ -169,4 +134,23 @@ public static class Utils { public static void SetCurrentLanguage(ulong guildId) { Messages.Culture = CultureInfoCache[Boyfriend.GetGuildConfig(guildId)["Lang"]]; } -} + + public static void SafeAppendToBuilder(StringBuilder appendTo, string appendWhat, SocketTextChannel? channel) { + if (channel == null) return; + if (appendTo.Length + appendWhat.Length > 2000) { + _ = SilentSendAsync(channel, appendTo.ToString()); + appendTo.Clear(); + } + + appendTo.AppendLine(appendWhat); + } + + public static void SafeAppendToBuilder(StringBuilder appendTo, string appendWhat, SocketUserMessage message) { + if (appendTo.Length + appendWhat.Length > 2000) { + _ = message.ReplyAsync(appendTo.ToString(), false, null, AllowedMentions.None); + appendTo.Clear(); + } + + appendTo.AppendLine(appendWhat); + } +} \ No newline at end of file