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; } }