diff --git a/Boyfriend-CSharp.sln b/Boyfriend-CSharp.sln index 003c58b..6178bea 100644 --- a/Boyfriend-CSharp.sln +++ b/Boyfriend-CSharp.sln @@ -1,5 +1,8 @@  Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32407.343 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Boyfriend", "Boyfriend\Boyfriend.csproj", "{21640A7A-75C2-4515-A1DF-CE8B6EEBD260}" EndProject Global @@ -13,4 +16,10 @@ Global {21640A7A-75C2-4515-A1DF-CE8B6EEBD260}.Release|Any CPU.ActiveCfg = Release|Any CPU {21640A7A-75C2-4515-A1DF-CE8B6EEBD260}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2EDBF9CE-35F0-4810-93F7-FE0159EFD865} + EndGlobalSection EndGlobal diff --git a/Boyfriend/Boyfriend.cs b/Boyfriend/Boyfriend.cs index 96d4df8..8ff5ca8 100644 --- a/Boyfriend/Boyfriend.cs +++ b/Boyfriend/Boyfriend.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System.Collections.ObjectModel; +using System.Text; using Discord; using Discord.WebSocket; using Newtonsoft.Json; @@ -6,6 +7,8 @@ using Newtonsoft.Json; namespace Boyfriend; public static class Boyfriend { + public static readonly StringBuilder StringBuilder = new(); + private static readonly Dictionary GuildCache = new(); private static readonly DiscordSocketConfig Config = new() { MessageCacheSize = 250, @@ -13,8 +16,34 @@ public static class Boyfriend { }; public static readonly DiscordSocketClient Client = new(Config); + private static readonly Game Activity = new("Retrospecter - Genocide", ActivityType.Listening); - private static readonly Dictionary GuildConfigDictionary = new(); + private static readonly Dictionary> GuildConfigDictionary = new(); + private static readonly Dictionary>> RemovedRolesDictionary = + new(); + + private static readonly Dictionary EmptyGuildConfig = new(); + private static readonly Dictionary> EmptyRemovedRoles = new(); + + public static readonly Dictionary DefaultConfig = new() { + {"Lang", "en"}, + {"Prefix", "!"}, + {"RemoveRolesOnMute", "false"}, + {"SendWelcomeMessages", "true"}, + {"ReceiveStartupMessages", "false"}, + {"FrowningFace", "true"}, + {"WelcomeMessage", Messages.DefaultWelcomeMessage}, + {"EventStartedReceivers", "interested,role"}, + {"StarterRole", "0"}, + {"MuteRole", "0"}, + {"EventNotifyReceiverRole", "0"}, + {"AdminLogChannel", "0"}, + {"BotLogChannel", "0"}, + {"EventCreatedChannel", "0"}, + {"EventStartedChannel", "0"}, + {"EventCancelledChannel", "0"}, + {"EventCompletedChannel", "0"} + }; public static void Main() { Init().GetAwaiter().GetResult(); @@ -27,7 +56,7 @@ public static class Boyfriend { await Client.LoginAsync(TokenType.Bot, token); await Client.StartAsync(); - await Client.SetActivityAsync(new Game("Retrospecter - Expurgation", ActivityType.Listening)); + await Client.SetActivityAsync(Activity); new EventHandler().InitEvents(); @@ -40,37 +69,65 @@ public static class Boyfriend { return Task.CompletedTask; } - public static async Task SetupGuildConfigs() { - foreach (var guild in Client.Guilds) { - var path = "config_" + guild.Id + ".json"; - if (!File.Exists(path)) File.Create(path); + public static async Task WriteGuildConfig(ulong id) { + var json = JsonConvert.SerializeObject(GuildConfigDictionary[id], Formatting.Indented); + var removedRoles = JsonConvert.SerializeObject(RemovedRolesDictionary[id], Formatting.Indented); - var config = JsonConvert.DeserializeObject(await File.ReadAllTextAsync(path)); - if (config == null) { - Messages.Culture = new CultureInfo("ru"); - config = new GuildConfig(guild.Id); - } - config.Validate(); - - GuildConfigDictionary.Add(config.Id.GetValueOrDefault(0), config); - } + await File.WriteAllTextAsync($"config_{id}.json", json); + await File.WriteAllTextAsync($"removedroles_{id}.json", removedRoles); } - public static GuildConfig GetGuildConfig(IGuild guild) { - Messages.Culture = new CultureInfo("ru"); + public static Dictionary GetGuildConfig(ulong id) { + if (!RemovedRolesDictionary.ContainsKey(id)) + RemovedRolesDictionary.Add(id, EmptyRemovedRoles); - var config = GuildConfigDictionary.ContainsKey(guild.Id) ? GuildConfigDictionary[guild.Id] - : new GuildConfig(guild.Id); - config.Validate(); + if (GuildConfigDictionary.ContainsKey(id)) return GuildConfigDictionary[id]; + + var path = $"config_{id}.json"; + + if (!File.Exists(path)) File.Create(path).Dispose(); + + var json = File.ReadAllText(path); + var config = JsonConvert.DeserializeObject>(json) ?? EmptyGuildConfig; + + foreach (var key in DefaultConfig.Keys) + if (!config.ContainsKey(key)) + config.Add(key, DefaultConfig[key]); + + foreach (var key in config.Keys) + if (!DefaultConfig.ContainsKey(key)) + config.Remove(key); + + GuildConfigDictionary.Add(id, config); return config; } - public static IGuild FindGuild(IMessageChannel channel) { + public static Dictionary> GetRemovedRoles(ulong id) { + if (RemovedRolesDictionary.ContainsKey(id)) return RemovedRolesDictionary[id]; + + var path = $"removedroles_{id}.json"; + + if (!File.Exists(path)) File.Create(path); + + var json = File.ReadAllText(path); + var removedRoles = JsonConvert.DeserializeObject>>(json) ?? + EmptyRemovedRoles; + + RemovedRolesDictionary.Add(id, removedRoles); + + return removedRoles; + } + + public static SocketGuild FindGuild(ulong channel) { + if (GuildCache.ContainsKey(channel)) return GuildCache[channel]; foreach (var guild in Client.Guilds) - if (guild.Channels.Any(x => x == channel)) - return guild; + foreach (var x in guild.Channels) { + if (x.Id != channel) continue; + GuildCache.Add(channel, guild); + return guild; + } throw new Exception(Messages.CouldntFindGuildByChannel); } -} \ No newline at end of file +} diff --git a/Boyfriend/Boyfriend.csproj b/Boyfriend/Boyfriend.csproj index cd969e0..a05d38d 100644 --- a/Boyfriend/Boyfriend.csproj +++ b/Boyfriend/Boyfriend.csproj @@ -15,8 +15,10 @@ - - + + + + diff --git a/Boyfriend/CommandHandler.cs b/Boyfriend/CommandHandler.cs index 610b69a..7754d53 100644 --- a/Boyfriend/CommandHandler.cs +++ b/Boyfriend/CommandHandler.cs @@ -1,4 +1,5 @@ -using System.Text.RegularExpressions; +using System.Text; +using System.Text.RegularExpressions; using Boyfriend.Commands; using Discord; using Discord.Commands; @@ -7,75 +8,104 @@ 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(); + + 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; // HOW IT CAN BE PRIVATE???? +#pragma warning restore CA2211 + public static async Task HandleCommand(SocketUserMessage message) { + StackedReplyMessage.Clear(); + StackedPrivateFeedback.Clear(); + StackedPublicFeedback.Clear(); var context = new SocketCommandContext(Boyfriend.Client, message); + var guild = context.Guild; + var config = Boyfriend.GetGuildConfig(guild.Id); - foreach (var command in Commands) { - var regex = new Regex(Regex.Escape(Boyfriend.GetGuildConfig(context.Guild).Prefix!)); - if (!command.GetAliases().Contains(regex.Replace(message.Content, "", 1).Split()[0])) - continue; + 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 args = message.Content.Split().Skip(1).ToArray(); - try { - if (command.GetArgumentsAmountRequired() > args.Length) - throw new ApplicationException(string.Format(Messages.NotEnoughArguments, - command.GetArgumentsAmountRequired(), args.Length)); - await command.Run(context, args); - } catch (Exception e) { - var signature = e switch { - ApplicationException => ":x:", - UnauthorizedAccessException => ":no_entry_sign:", - _ => ":stop_sign:" - }; - var stacktrace = e.StackTrace; - var toSend = $"{signature} `{e.Message}`"; - if (stacktrace != null && e is not ApplicationException && e is not UnauthorizedAccessException) - toSend += $"{Environment.NewLine}{Utils.Wrap(stacktrace)}"; - await context.Channel.SendMessageAsync(toSend); - throw; + var list = message.Content.Split("\n"); + var currentLine = 0; + foreach (var line in list) { + currentLine++; + foreach (var command in Commands) { + if (!command.Aliases.Contains(regex.Replace(line, "", 1).ToLower().Split()[0])) + continue; + + await context.Channel.TriggerTypingAsync(); + + var args = line.Split().Skip(1).ToArray(); + + if (command.ArgsLengthRequired <= args.Length) + await command.Run(context, args); + else + StackedReplyMessage.AppendFormat(Messages.NotEnoughArguments, command.ArgsLengthRequired.ToString(), + args.Length.ToString()); + + if (currentLine != list.Length) continue; + if (ConfigWriteScheduled) await Boyfriend.WriteGuildConfig(guild.Id); + await context.Message.ReplyAsync(StackedReplyMessage.ToString(), false, null, AllowedMentions.None); + + var adminChannel = Utils.GetAdminLogChannel(guild.Id); + var systemChannel = guild.SystemChannel; + if (adminChannel != null) + await Utils.SilentSendAsync(adminChannel, StackedPrivateFeedback.ToString()); + if (systemChannel != null) + await Utils.SilentSendAsync(systemChannel, StackedPublicFeedback.ToString()); } - - break; } } - public static async Task CheckPermissions(IGuildUser user, GuildPermission toCheck, + public static string HasPermission(ref SocketGuildUser user, GuildPermission toCheck, GuildPermission forBot = GuildPermission.StartEmbeddedActivities) { - var me = await user.Guild.GetCurrentUserAsync(); + 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 (user.Id != user.Guild.OwnerId - && (!me.GuildPermissions.Has(GuildPermission.Administrator) - || !user.GuildPermissions.Has(GuildPermission.Administrator))) { - if (!me.GuildPermissions.Has(forBot)) - throw new UnauthorizedAccessException(Messages.CommandNoPermissionBot); - if (!user.GuildPermissions.Has(toCheck)) - throw new UnauthorizedAccessException(Messages.CommandNoPermissionUser); - } + if (!me.GuildPermissions.Has(forBot)) + return Messages.CommandNoPermissionBot; + + return !user.GuildPermissions.Has(toCheck) ? Messages.CommandNoPermissionUser : ""; } - public static async Task CheckInteractions(IGuildUser actor, IGuildUser target) { - var me = await target.Guild.GetCurrentUserAsync(); - + public static string CanInteract(ref SocketGuildUser actor, ref SocketGuildUser target) { if (actor.Guild != target.Guild) - throw new Exception(Messages.InteractionsDifferentGuilds); - if (actor.Id == actor.Guild.OwnerId) return; + return Messages.InteractionsDifferentGuilds; + if (actor.Id == actor.Guild.OwnerId) + return ""; if (target.Id == target.Guild.OwnerId) - throw new UnauthorizedAccessException(Messages.InteractionsOwner); + return Messages.InteractionsOwner; if (actor == target) - throw new UnauthorizedAccessException(Messages.InteractionsYourself); + return Messages.InteractionsYourself; + + var me = target.Guild.CurrentUser; + if (target == me) - throw new UnauthorizedAccessException(Messages.InteractionsMe); + return Messages.InteractionsMe; if (me.Hierarchy <= target.Hierarchy) - throw new UnauthorizedAccessException(Messages.InteractionsFailedBot); - if (actor.Hierarchy <= target.Hierarchy) - throw new UnauthorizedAccessException(Messages.InteractionsFailedUser); + return Messages.InteractionsFailedBot; + + return actor.Hierarchy <= target.Hierarchy ? Messages.InteractionsFailedUser : ""; } } diff --git a/Boyfriend/Commands/BanCommand.cs b/Boyfriend/Commands/BanCommand.cs index 60e945f..80e8b0c 100644 --- a/Boyfriend/Commands/BanCommand.cs +++ b/Boyfriend/Commands/BanCommand.cs @@ -1,73 +1,72 @@ using Discord; using Discord.Commands; - -// ReSharper disable UnusedType.Global -// ReSharper disable UnusedMember.Global -// ReSharper disable ClassNeverInstantiated.Global +using Discord.WebSocket; namespace Boyfriend.Commands; public class BanCommand : Command { - public override async Task Run(SocketCommandContext context, string[] args) { - var reason = Utils.JoinString(args, 1); + public override string[] Aliases { get; } = {"ban", "бан"}; + public override int ArgsLengthRequired => 2; - TimeSpan duration; - try { - duration = Utils.GetTimeSpan(args[1]); - reason = Utils.JoinString(args, 2); - } catch (Exception e) when (e is ArgumentNullException or FormatException or OverflowException) { - await Warn(context.Channel as ITextChannel, Messages.DurationParseFailed); - duration = TimeSpan.FromMilliseconds(-1); + public override async Task Run(SocketCommandContext context, string[] args) { + var toBan = Utils.ParseUser(args[0]); + + if (toBan == null) { + Error(Messages.UserDoesntExist, false); + return; } - await BanUser(context.Guild, context.Channel as ITextChannel, context.Guild.GetUser(context.User.Id), - await Utils.ParseUser(args[0]), duration, reason); + var guild = context.Guild; + var author = (SocketGuildUser) context.User; + + var permissionCheckResponse = CommandHandler.HasPermission(ref author, GuildPermission.BanMembers); + if (permissionCheckResponse != "") { + 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 != "") { + 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); + } + + await BanUser(guild, author, toBan, duration, reason); } - public static async Task BanUser(IGuild guild, ITextChannel? channel, IGuildUser author, IUser toBan, - TimeSpan duration, string reason) { - var authorMention = author.Mention; - var guildBanMessage = $"({Utils.GetNameAndDiscrim(author)}) {reason}"; - var memberToBan = await guild.GetUserAsync(toBan.Id); - var expiresIn = duration.TotalSeconds > 0 ? string.Format(Messages.PunishmentExpiresIn, Environment.NewLine, - DateTimeOffset.Now.ToUnixTimeSeconds() + duration.TotalSeconds) : ""; - var notification = string.Format(Messages.UserBanned, authorMention, toBan.Mention, Utils.WrapInline(reason), - expiresIn); - - await CommandHandler.CheckPermissions(author, GuildPermission.BanMembers); - if (memberToBan != null) - await CommandHandler.CheckInteractions(author, memberToBan); + public static async Task BanUser(SocketGuild guild, SocketGuildUser author, SocketUser toBan, TimeSpan duration, + string reason) { + var guildBanMessage = $"({author}) {reason}"; await Utils.SendDirectMessage(toBan, string.Format(Messages.YouWereBanned, author.Mention, guild.Name, Utils.WrapInline(reason))); await guild.AddBanAsync(toBan, 0, guildBanMessage); - await Utils.SilentSendAsync(channel, - string.Format(Messages.BanResponse, toBan.Mention, Utils.WrapInline(reason))); - await Utils.SilentSendAsync(await guild.GetSystemChannelAsync(), notification); - await Utils.SilentSendAsync(await Utils.GetAdminLogChannel(guild), notification); + var feedback = string.Format(Messages.FeedbackUserBanned, toBan.Mention, + Utils.GetHumanizedTimeOffset(ref duration), Utils.WrapInline(reason)); + Success(feedback, author.Mention, false, false); + await Utils.SendFeedback(feedback, guild.Id, author.Mention, true); if (duration.TotalSeconds > 0) { - await Task.Run(async () => { + async void DelayUnban() { await Task.Delay(duration); - try { - await UnbanCommand.UnbanUser(guild, null, await guild.GetCurrentUserAsync(), toBan, - Messages.PunishmentExpired); - } catch (ApplicationException) {} - }); + await UnbanCommand.UnbanUser(guild, guild.CurrentUser, toBan, Messages.PunishmentExpired); + } + + var task = new Task(DelayUnban); + task.Start(); } } - - public override List GetAliases() { - return new List {"ban", "бан"}; - } - - public override int GetArgumentsAmountRequired() { - return 2; - } - - public override string GetSummary() { - return "Банит пользователя"; - } -} \ No newline at end of file +} diff --git a/Boyfriend/Commands/ClearCommand.cs b/Boyfriend/Commands/ClearCommand.cs index 473d8d6..504c396 100644 --- a/Boyfriend/Commands/ClearCommand.cs +++ b/Boyfriend/Commands/ClearCommand.cs @@ -1,54 +1,46 @@ using Discord; using Discord.Commands; - -// ReSharper disable UnusedType.Global -// ReSharper disable UnusedMember.Global -// ReSharper disable ClassNeverInstantiated.Global +using Discord.WebSocket; namespace Boyfriend.Commands; public class ClearCommand : Command { - public override async Task Run(SocketCommandContext context, string[] args) { - var user = context.User; + public override string[] Aliases { get; } = {"clear", "purge", "очистить", "стереть"}; + public override int ArgsLengthRequired => 1; - int toDelete; - try { - toDelete = Convert.ToInt32(args[0]); - } catch (Exception e) when (e is FormatException or OverflowException) { - throw new ApplicationException(Messages.ClearInvalidAmountSpecified); + public override async Task Run(SocketCommandContext context, string[] args) { + var user = (SocketGuildUser) context.User; + + if (context.Channel is not SocketTextChannel channel) throw new Exception(); + + var permissionCheckResponse = CommandHandler.HasPermission(ref user, GuildPermission.ManageMessages); + if (permissionCheckResponse != "") { + Error(permissionCheckResponse, true); + return; } - if (context.Channel is not ITextChannel channel) return; - - await CommandHandler.CheckPermissions(context.Guild.GetUser(user.Id), GuildPermission.ManageMessages); + if (!int.TryParse(args[0], out var toDelete)) { + Error(Messages.ClearInvalidAmountSpecified, false); + return; + } switch (toDelete) { case < 1: - throw new ApplicationException(Messages.ClearNegativeAmount); + Error(Messages.ClearNegativeAmount, false); + break; case > 200: - throw new ApplicationException(Messages.ClearAmountTooLarge); - default: { + Error(Messages.ClearAmountTooLarge, false); + break; + default: var messages = await channel.GetMessagesAsync(toDelete + 1).FlattenAsync(); - await channel.DeleteMessagesAsync(messages, Utils.GetRequestOptions(Utils.GetNameAndDiscrim(user))); + 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); - await Utils.SilentSendAsync(await Utils.GetAdminLogChannel(context.Guild), - string.Format(Messages.MessagesDeleted, user.Mention, toDelete + 1, - Utils.MentionChannel(context.Channel.Id))); break; - } } } - - public override List GetAliases() { - return new List {"clear", "purge", "очистить", "стереть"}; - } - - public override int GetArgumentsAmountRequired() { - return 1; - } - - public override string GetSummary() { - return "Очищает сообщения"; - } -} +} \ No newline at end of file diff --git a/Boyfriend/Commands/Command.cs b/Boyfriend/Commands/Command.cs index af2c845..4903177 100644 --- a/Boyfriend/Commands/Command.cs +++ b/Boyfriend/Commands/Command.cs @@ -1,18 +1,32 @@ -using Discord; +using System.Text; using Discord.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); - public abstract List GetAliases(); + protected static void Output(ref StringBuilder message) { + CommandHandler.StackedReplyMessage.Append(message).AppendLine(); + } - public abstract int GetArgumentsAmountRequired(); + 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); + } - public abstract string GetSummary(); + protected static void Warn(string message) { + CommandHandler.StackedReplyMessage.Append(":warning: ").AppendLine(message); + } - protected static async Task Warn(ITextChannel? channel, string warning) { - await Utils.SilentSendAsync(channel, ":warning: " + warning); + protected static void Error(string message, bool accessDenied) { + var symbol = accessDenied ? ":no_entry_sign: " : ":x: "; + CommandHandler.StackedReplyMessage.Append(symbol).AppendLine(message); } } \ No newline at end of file diff --git a/Boyfriend/Commands/HelpCommand.cs b/Boyfriend/Commands/HelpCommand.cs index 50b9818..656fa48 100644 --- a/Boyfriend/Commands/HelpCommand.cs +++ b/Boyfriend/Commands/HelpCommand.cs @@ -1,30 +1,22 @@ using Discord.Commands; - -// ReSharper disable UnusedType.Global -// ReSharper disable UnusedMember.Global +using Humanizer; namespace Boyfriend.Commands; public class HelpCommand : Command { - public override async Task Run(SocketCommandContext context, string[] args) { - var nl = Environment.NewLine; - var prefix = Boyfriend.GetGuildConfig(context.Guild).Prefix; - var toSend = string.Format(Messages.CommandHelp, nl); + public override string[] Aliases { get; } = {"help", "помощь", "справка"}; + public override int ArgsLengthRequired => 0; - toSend = CommandHandler.Commands.Aggregate(toSend, - (current, command) => current + $"`{prefix}{command.GetAliases()[0]}`: {command.GetSummary()}{nl}"); - await context.Channel.SendMessageAsync(toSend); - } + public override Task Run(SocketCommandContext context, string[] args) { + var prefix = Boyfriend.GetGuildConfig(context.Guild.Id)["Prefix"]; + var toSend = Boyfriend.StringBuilder.Append(Messages.CommandHelp); - public override List GetAliases() { - return new List {"help", "помощь", "справка"}; - } + foreach (var command in CommandHandler.Commands) + toSend.Append( + $"\n`{prefix}{command.Aliases[0]}`: {Utils.GetMessage($"CommandDescription{command.Aliases[0].Titleize()}")}"); + Output(ref toSend); + toSend.Clear(); - public override int GetArgumentsAmountRequired() { - return 0; + return Task.CompletedTask; } - - public override string GetSummary() { - return "Показывает эту справку"; - } -} +} \ No newline at end of file diff --git a/Boyfriend/Commands/KickCommand.cs b/Boyfriend/Commands/KickCommand.cs index 0145ebc..ffa61eb 100644 --- a/Boyfriend/Commands/KickCommand.cs +++ b/Boyfriend/Commands/KickCommand.cs @@ -1,50 +1,49 @@ using Discord; using Discord.Commands; - -// ReSharper disable UnusedType.Global -// ReSharper disable UnusedMember.Global -// ReSharper disable ClassNeverInstantiated.Global +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 = context.Guild.GetUser(context.User.Id); - var toKick = await Utils.ParseMember(context.Guild, args[0]); + var author = (SocketGuildUser) context.User; - await CommandHandler.CheckPermissions(author, GuildPermission.KickMembers); - await CommandHandler.CheckInteractions(author, toKick); + var permissionCheckResponse = CommandHandler.HasPermission(ref author, GuildPermission.KickMembers); + if (permissionCheckResponse != "") { + Error(permissionCheckResponse, true); + return; + } - await KickMember(context.Guild, context.Channel as ITextChannel, author, toKick, Utils.JoinString(args, 1)); + 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 != "") { + Error(interactionCheckResponse, true); + return; + } + + await KickMember(context.Guild, author, toKick, Utils.JoinString(ref args, 1)); + + Success( + string.Format(Messages.FeedbackMemberKicked, toKick.Mention, + Utils.WrapInline(Utils.JoinString(ref args, 1))), author.Mention); } - private static async Task KickMember(IGuild guild, ITextChannel? channel, IUser author, IGuildUser toKick, - string reason) { + private static async Task KickMember(IGuild guild, SocketUser author, SocketGuildUser toKick, string reason) { var authorMention = author.Mention; - var guildKickMessage = $"({Utils.GetNameAndDiscrim(author)}) {reason}"; - var notification = string.Format(Messages.MemberKicked, authorMention, toKick.Mention, - Utils.WrapInline(reason)); + var guildKickMessage = $"({author}) {reason}"; - await Utils.SendDirectMessage(toKick, string.Format(Messages.YouWereKicked, authorMention, guild.Name, - Utils.WrapInline(reason))); + await Utils.SendDirectMessage(toKick, + string.Format(Messages.YouWereKicked, authorMention, guild.Name, Utils.WrapInline(reason))); await toKick.KickAsync(guildKickMessage); - - await Utils.SilentSendAsync(channel, string.Format(Messages.KickResponse, toKick.Mention, - Utils.WrapInline(reason))); - await Utils.SilentSendAsync(await guild.GetSystemChannelAsync(), notification); - await Utils.SilentSendAsync(await Utils.GetAdminLogChannel(guild), notification); } - - public override List GetAliases() { - return new List {"kick", "кик"}; - } - - public override int GetArgumentsAmountRequired() { - return 2; - } - - public override string GetSummary() { - return "Выгоняет участника"; - } -} +} \ No newline at end of file diff --git a/Boyfriend/Commands/MuteCommand.cs b/Boyfriend/Commands/MuteCommand.cs index 4ad55e8..144d76f 100644 --- a/Boyfriend/Commands/MuteCommand.cs +++ b/Boyfriend/Commands/MuteCommand.cs @@ -1,117 +1,122 @@ using Discord; using Discord.Commands; using Discord.Net; - -// ReSharper disable UnusedType.Global -// ReSharper disable UnusedMember.Global -// ReSharper disable ClassNeverInstantiated.Global +using Discord.WebSocket; namespace Boyfriend.Commands; public class MuteCommand : Command { - public override async Task Run(SocketCommandContext context, string[] args) { - var author = context.Guild.GetUser(context.User.Id); - var config = Boyfriend.GetGuildConfig(context.Guild); - var reason = Utils.JoinString(args, 1); - var role = Utils.GetMuteRole(context.Guild); - var rolesRemoved = config.RolesRemovedOnMute!; - var toMute = await Utils.ParseMember(context.Guild, args[0]); + public override string[] Aliases { get; } = {"mute", "timeout", "заглушить", "мут"}; + public override int ArgsLengthRequired => 2; - TimeSpan duration; - try { - duration = Utils.GetTimeSpan(args[1]); - reason = Utils.JoinString(args, 2); - } catch (Exception e) when (e is ArgumentNullException or FormatException or OverflowException) { - await Warn(context.Channel as ITextChannel, Messages.DurationParseFailed); - duration = TimeSpan.FromMilliseconds(-1); + public override async Task Run(SocketCommandContext context, string[] args) { + var toMute = Utils.ParseMember(context.Guild, args[0]); + var reason = Utils.JoinString(ref args, 2); + + var duration = Utils.GetTimeSpan(ref args[1]) ?? TimeSpan.FromMilliseconds(-1); + if (duration.TotalSeconds < 0) { + Warn(Messages.DurationParseFailed); + reason = Utils.JoinString(ref args, 1); } - if (toMute == null) - throw new ApplicationException(Messages.UserNotInGuild); + if (toMute == null) { + Error(Messages.UserNotInGuild, false); + return; + } - if (role != null && toMute.RoleIds.Any(x => x == role.Id) || toMute.TimedOutUntil != null && - toMute.TimedOutUntil.Value.ToUnixTimeMilliseconds() > DateTimeOffset.Now.ToUnixTimeMilliseconds()) - throw new ApplicationException(Messages.MemberAlreadyMuted); + var guild = context.Guild; + var role = Utils.GetMuteRole(ref guild); + + if (role != null) { + var hasMuteRole = false; + foreach (var x in toMute.Roles) { + if (x != role) continue; + hasMuteRole = true; + break; + } + + if (hasMuteRole || (toMute.TimedOutUntil != null && toMute.TimedOutUntil.Value.ToUnixTimeMilliseconds() > + DateTimeOffset.Now.ToUnixTimeMilliseconds())) { + Error(Messages.MemberAlreadyMuted, false); + return; + } + } + + var rolesRemoved = Boyfriend.GetRemovedRoles(context.Guild.Id); if (rolesRemoved.ContainsKey(toMute.Id)) { foreach (var roleId in rolesRemoved[toMute.Id]) await toMute.AddRoleAsync(roleId); rolesRemoved.Remove(toMute.Id); - await config.Save(); - throw new ApplicationException(Messages.RolesReturned); + CommandHandler.ConfigWriteScheduled = true; + Warn(Messages.RolesReturned); } - await CommandHandler.CheckPermissions(author, GuildPermission.ModerateMembers, GuildPermission.ManageRoles); - await CommandHandler.CheckInteractions(author, toMute); + var author = (SocketGuildUser) context.User; - await MuteMember(context.Guild, context.Channel as ITextChannel, context.Guild.GetUser(context.User.Id), toMute, - duration, reason); + var permissionCheckResponse = CommandHandler.HasPermission(ref author, GuildPermission.ModerateMembers); + if (permissionCheckResponse != "") { + Error(permissionCheckResponse, true); + return; + } + + var interactionCheckResponse = CommandHandler.CanInteract(ref author, ref toMute); + if (interactionCheckResponse != "") { + Error(interactionCheckResponse, true); + return; + } + + await MuteMember(guild, author, toMute, duration, reason); + + Success( + string.Format(Messages.FeedbackMemberMuted, toMute.Mention, Utils.GetHumanizedTimeOffset(ref duration), + Utils.WrapInline(reason)), author.Mention, true); } - private static async Task MuteMember(IGuild guild, ITextChannel? channel, IGuildUser author, IGuildUser toMute, + private static async Task MuteMember(SocketGuild guild, SocketUser author, SocketGuildUser toMute, TimeSpan duration, string reason) { - await CommandHandler.CheckPermissions(author, GuildPermission.ManageMessages, GuildPermission.ManageRoles); - var authorMention = author.Mention; - var config = Boyfriend.GetGuildConfig(guild); - var requestOptions = Utils.GetRequestOptions($"({Utils.GetNameAndDiscrim(author)}) {reason}"); - var role = Utils.GetMuteRole(guild); + var config = Boyfriend.GetGuildConfig(guild.Id); + var requestOptions = Utils.GetRequestOptions($"({author}) {reason}"); + var role = Utils.GetMuteRole(ref guild); var hasDuration = duration.TotalSeconds > 0; - var expiresIn = hasDuration ? string.Format(Messages.PunishmentExpiresIn, Environment.NewLine, - DateTimeOffset.Now.ToUnixTimeSeconds() + duration.TotalSeconds) : ""; - var notification = string.Format(Messages.MemberMuted, authorMention, toMute.Mention, Utils.WrapInline(reason), - expiresIn); if (role != null) { - if (config.RemoveRolesOnMute.GetValueOrDefault(false)) { + if (config["RemoveRolesOnMute"] == "true") { var rolesRemoved = new List(); - foreach (var roleId in toMute.RoleIds) { + foreach (var userRole in toMute.Roles) try { - if (roleId == guild.Id) continue; - if (roleId == role.Id) continue; - await toMute.RemoveRoleAsync(roleId); - rolesRemoved.Add(roleId); + if (userRole == guild.EveryoneRole || userRole == role) continue; + await toMute.RemoveRoleAsync(role); + rolesRemoved.Add(userRole.Id); } catch (HttpException e) { - await Warn(channel, - string.Format(Messages.RoleRemovalFailed, $"<@&{roleId}>", Utils.WrapInline(e.Reason))); + Warn(string.Format(Messages.RoleRemovalFailed, $"<@&{userRole}>", Utils.WrapInline(e.Reason))); } - } - config.RolesRemovedOnMute!.Add(toMute.Id, rolesRemoved); - await config.Save(); + Boyfriend.GetRemovedRoles(guild.Id).Add(toMute.Id, rolesRemoved.AsReadOnly()); + CommandHandler.ConfigWriteScheduled = true; - if (hasDuration) - await Task.Run(async () => { + if (hasDuration) { + async void DelayUnmute() { await Task.Delay(duration); - try { - await UnmuteCommand.UnmuteMember(guild, null, await guild.GetCurrentUserAsync(), toMute, - Messages.PunishmentExpired); - } catch (ApplicationException) {} - }); + await UnmuteCommand.UnmuteMember(guild, guild.CurrentUser, toMute, Messages.PunishmentExpired); + } + + var task = new Task(DelayUnmute); + task.Start(); + } } await toMute.AddRoleAsync(role, requestOptions); } else { - if (!hasDuration) - throw new ApplicationException(Messages.DurationRequiredForTimeOuts); - if (toMute.IsBot) - throw new ApplicationException(Messages.CannotTimeOutBot); + if (!hasDuration) { + Error(Messages.DurationRequiredForTimeOuts, false); + return; + } + if (toMute.IsBot) { + Error(Messages.CannotTimeOutBot, false); + return; + } await toMute.SetTimeOutAsync(duration, requestOptions); } - await Utils.SilentSendAsync(channel, - string.Format(Messages.MuteResponse, toMute.Mention, Utils.WrapInline(reason))); - await Utils.SilentSendAsync(await guild.GetSystemChannelAsync(), notification); - await Utils.SilentSendAsync(await Utils.GetAdminLogChannel(guild), notification); - } - - public override List GetAliases() { - return new List {"mute", "мут", "мьют"}; - } - - public override int GetArgumentsAmountRequired() { - return 2; - } - - public override string GetSummary() { - return "Глушит участника"; } } \ No newline at end of file diff --git a/Boyfriend/Commands/PingCommand.cs b/Boyfriend/Commands/PingCommand.cs index ae0c4af..c5189e4 100644 --- a/Boyfriend/Commands/PingCommand.cs +++ b/Boyfriend/Commands/PingCommand.cs @@ -1,25 +1,19 @@ using Discord.Commands; -// ReSharper disable UnusedType.Global -// ReSharper disable UnusedMember.Global - namespace Boyfriend.Commands; public class PingCommand : Command { - public override async Task Run(SocketCommandContext context, string[] args) { - await context.Channel.SendMessageAsync($"{Utils.GetBeep(Boyfriend.GetGuildConfig(context.Guild).Lang!)}" + - $"{Boyfriend.Client.Latency}{Messages.Milliseconds}"); - } + public override string[] Aliases { get; } = {"ping", "latency", "pong", "пинг", "задержка", "понг"}; + public override int ArgsLengthRequired => 0; - public override List GetAliases() { - return new List {"ping", "пинг", "задержка"}; - } + public override Task Run(SocketCommandContext context, string[] args) { + var builder = Boyfriend.StringBuilder; - public override int GetArgumentsAmountRequired() { - return 0; - } + builder.Append(Utils.GetBeep()).Append(Boyfriend.Client.Latency).Append(Messages.Milliseconds); - public override string GetSummary() { - return "Измеряет время обработки REST-запроса"; + Output(ref builder); + builder.Clear(); + + return Task.CompletedTask; } -} +} \ No newline at end of file diff --git a/Boyfriend/Commands/SettingsCommand.cs b/Boyfriend/Commands/SettingsCommand.cs index c0e189a..368c5d8 100644 --- a/Boyfriend/Commands/SettingsCommand.cs +++ b/Boyfriend/Commands/SettingsCommand.cs @@ -1,111 +1,156 @@ -using System.Reflection; -using Discord; +using Discord; using Discord.Commands; - -// ReSharper disable UnusedType.Global -// ReSharper disable UnusedMember.Global +using Discord.WebSocket; namespace Boyfriend.Commands; public class SettingsCommand : Command { - public override async Task Run(SocketCommandContext context, string[] args) { - var config = Boyfriend.GetGuildConfig(context.Guild); - var guild = context.Guild; + public override string[] Aliases { get; } = {"settings", "config", "настройки", "конфиг"}; + public override int ArgsLengthRequired => 0; - await CommandHandler.CheckPermissions(context.Guild.GetUser(context.User.Id), GuildPermission.ManageGuild); + public override Task Run(SocketCommandContext context, string[] args) { + var author = (SocketGuildUser) context.User; + + var permissionCheckResponse = CommandHandler.HasPermission(ref author, GuildPermission.ManageGuild); + if (permissionCheckResponse != "") { + Error(permissionCheckResponse, true); + return Task.CompletedTask; + } + + var guild = context.Guild; + var config = Boyfriend.GetGuildConfig(guild.Id); if (args.Length == 0) { - var nl = Environment.NewLine; - dynamic forCheck; - var adminLogChannel = (forCheck = guild.GetTextChannel(config.AdminLogChannel.GetValueOrDefault(0))) == null - ? Messages.ChannelNotSpecified : forCheck.Mention; - var botLogChannel = (forCheck = guild.GetTextChannel(config.BotLogChannel.GetValueOrDefault(0))) == null - ? Messages.ChannelNotSpecified : forCheck.Mention; - var muteRole = (forCheck = guild.GetRole(config.MuteRole.GetValueOrDefault(0))) == null - ? Messages.RoleNotSpecified : forCheck.Mention; - var defaultRole = (forCheck = guild.GetRole(config.DefaultRole.GetValueOrDefault(0))) == null - ? Messages.RoleNotSpecified : forCheck.Mention; - var toSend = string.Format(Messages.CurrentSettings, nl) + - string.Format(Messages.CurrentSettingsLang, config.Lang, nl) + - string.Format(Messages.CurrentSettingsPrefix, config.Prefix, nl) + - string.Format(Messages.CurrentSettingsRemoveRoles, - YesOrNo(config.RemoveRolesOnMute.GetValueOrDefault(false)), nl) + - string.Format(Messages.CurrentSettingsUseSystemChannel, - YesOrNo(config.UseSystemChannel.GetValueOrDefault(true)), nl) + - string.Format(Messages.CurrentSettingsSendWelcomeMessages, - YesOrNo(config.SendWelcomeMessages.GetValueOrDefault(true)), nl) + - string.Format(Messages.CurrentSettingsReceiveStartupMessages, - YesOrNo(config.ReceiveStartupMessages.GetValueOrDefault(true)), nl) + - string.Format(Messages.CurrentSettingsWelcomeMessage, config.WelcomeMessage, nl) + - string.Format(Messages.CurrentSettingsDefaultRole, defaultRole, nl) + - string.Format(Messages.CurrentSettingsMuteRole, muteRole, nl) + - string.Format(Messages.CurrentSettingsAdminLogChannel, adminLogChannel, nl) + - string.Format(Messages.CurrentSettingsBotLogChannel, botLogChannel); - await Utils.SilentSendAsync(context.Channel as ITextChannel ?? throw new ApplicationException(), toSend); - return; - } + var currentSettings = Boyfriend.StringBuilder.AppendLine(Messages.CurrentSettings); - var setting = args[0].ToLower(); - var value = ""; + foreach (var setting in Boyfriend.DefaultConfig) { + var format = "{0}"; + var currentValue = config[setting.Key]; - if (args.Length >= 2) - try { - value = args[1].ToLower(); - } catch (IndexOutOfRangeException) { - throw new ApplicationException(Messages.InvalidSettingValue); + if (setting.Key.EndsWith("Channel")) { + if (guild.GetTextChannel(Convert.ToUInt64(currentValue)) != null) + format = "<#{0}>"; + else + currentValue = Messages.ChannelNotSpecified; + } else if (setting.Key.EndsWith("Role")) { + if (guild.GetRole(Convert.ToUInt64(currentValue)) != null) + format = "<@&{0}>"; + else + currentValue = Messages.RoleNotSpecified; + } else { + if (IsBool(currentValue)) + currentValue = YesOrNo(currentValue == "true"); + else + format = Utils.WrapInline("{0}")!; + } + + currentSettings.Append($"{Utils.GetMessage($"Settings{setting.Key}")} (`{setting.Key}`): ") + .AppendFormat(format, currentValue).AppendLine(); } - PropertyInfo? property = null; - foreach (var prop in typeof(GuildConfig).GetProperties()) - if (setting == prop.Name.ToLower()) - property = prop; - if (property == null || !property.CanWrite) - throw new ApplicationException(Messages.SettingDoesntExist); - var type = property.PropertyType; + Output(ref currentSettings); + currentSettings.Clear(); + return Task.CompletedTask; + } + + var selectedSetting = args[0].ToLower(); + + var exists = false; + foreach (var setting in Boyfriend.DefaultConfig) { + if (selectedSetting != setting.Key.ToLower()) continue; + selectedSetting = setting.Key; + exists = true; + break; + } + + if (!exists) { + Error(Messages.SettingDoesntExist, false); + return Task.CompletedTask; + } + + string value; + + if (args.Length >= 2) { + value = Utils.JoinString(ref args, 1); + if (selectedSetting != "WelcomeMessage") + value = value.Replace(" ", "").ToLower(); + if (value.StartsWith(",") || value.Count(x => x == ',') > 1) { + Error(Messages.InvalidSettingValue, false); + return Task.CompletedTask; + } + } else { + value = "reset"; + } + + if (IsBool(Boyfriend.DefaultConfig[selectedSetting]) && !IsBool(value)) { + value = value switch { + "y" or "yes" => "true", + "n" or "no" => "false", + _ => value + }; + if (!IsBool(value)) { + Error(Messages.InvalidSettingValue, false); + return Task.CompletedTask; + } + } + + var localizedSelectedSetting = Utils.GetMessage($"Settings{selectedSetting}"); + + var mention = Utils.ParseMention(value); + if (mention != 0) value = mention.ToString(); + + var formatting = Utils.WrapInline("{0}")!; + if (selectedSetting.EndsWith("Channel")) + formatting = "<#{0}>"; + if (selectedSetting.EndsWith("Role")) + formatting = "<@&{0}>"; + if (value is "0" or "reset" or "default") + formatting = Messages.SettingNotDefined; + var formattedValue = IsBool(value) ? YesOrNo(value == "true") : string.Format(formatting, value); if (value is "reset" or "default") { - property.SetValue(config, null); - } else if (type == typeof(string)) { - if (setting == "lang" && value is not ("ru" or "en")) - throw new ApplicationException(Messages.LanguageNotSupported); - property.SetValue(config, value); + config[selectedSetting] = Boyfriend.DefaultConfig[selectedSetting]; } else { - try { - if (type == typeof(bool?)) - property.SetValue(config, Convert.ToBoolean(value)); - - if (type == typeof(ulong?)) { - var id = Convert.ToUInt64(value); - if (property.Name.EndsWith("Channel") && guild.GetTextChannel(id) == null) - throw new ApplicationException(Messages.InvalidChannel); - if (property.Name.EndsWith("Role") && guild.GetRole(id) == null) - throw new ApplicationException(Messages.InvalidRole); - - property.SetValue(config, id); - } - } catch (Exception e) when (e is FormatException or OverflowException) { - throw new ApplicationException(Messages.InvalidSettingValue); + if (value == config[selectedSetting]) { + Error(string.Format(Messages.SettingsNothingChanged, localizedSelectedSetting, formattedValue), false); + return Task.CompletedTask; } - } - config.Validate(); - await config.Save(); - await context.Channel.SendMessageAsync(Messages.SettingsUpdated); + if (selectedSetting == "Lang" && value is not "ru" and not "en") { + Error(Messages.LanguageNotSupported, false); + return Task.CompletedTask; + } + + if (selectedSetting.EndsWith("Channel") && guild.GetTextChannel(mention) == null) { + Error(Messages.InvalidChannel, false); + return Task.CompletedTask; + } + + if (selectedSetting.EndsWith("Role") && guild.GetRole(mention) == null) { + Error(Messages.InvalidRole, false); + return Task.CompletedTask; + } + + config[selectedSetting] = value; + } + + if (selectedSetting == "Lang") { + Utils.SetCurrentLanguage(guild.Id); + localizedSelectedSetting = Utils.GetMessage($"Settings{selectedSetting}"); + } + + CommandHandler.ConfigWriteScheduled = true; + + Success(string.Format(Messages.FeedbackSettingsUpdated, localizedSelectedSetting, formattedValue), + author.Mention); + return Task.CompletedTask; } private static string YesOrNo(bool isYes) { return isYes ? Messages.Yes : Messages.No; } - public override List GetAliases() { - return new List {"settings", "настройки", "config", "конфиг "}; + private static bool IsBool(string value) { + return value is "true" or "false"; } - - public override int GetArgumentsAmountRequired() { - return 0; - } - - public override string GetSummary() { - return "Настраивает бота отдельно для этого сервера"; - } -} \ No newline at end of file +} diff --git a/Boyfriend/Commands/UnbanCommand.cs b/Boyfriend/Commands/UnbanCommand.cs index 69874de..8741a3e 100644 --- a/Boyfriend/Commands/UnbanCommand.cs +++ b/Boyfriend/Commands/UnbanCommand.cs @@ -1,48 +1,45 @@ using Discord; using Discord.Commands; - -// ReSharper disable UnusedType.Global -// ReSharper disable UnusedMember.Global -// ReSharper disable ClassNeverInstantiated.Global +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) { - await UnbanUser(context.Guild, context.Channel as ITextChannel, context.Guild.GetUser(context.User.Id), - await Utils.ParseUser(args[0]), Utils.JoinString(args, 1)); + var author = (SocketGuildUser) context.User; + + var permissionCheckResponse = CommandHandler.HasPermission(ref author, GuildPermission.BanMembers); + if (permissionCheckResponse != "") { + Error(permissionCheckResponse, true); + 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); } - public static async Task UnbanUser(IGuild guild, ITextChannel? channel, IGuildUser author, IUser toUnban, - string reason) { - - var authorMention = author.Mention; - var notification = string.Format(Messages.UserUnbanned, authorMention, toUnban.Mention, - Utils.WrapInline(reason)); - var requestOptions = Utils.GetRequestOptions($"({Utils.GetNameAndDiscrim(author)}) {reason}"); - - await CommandHandler.CheckPermissions(author, GuildPermission.BanMembers); - - if (guild.GetBanAsync(toUnban.Id) == null) - throw new ApplicationException(Messages.UserNotBanned); + public static async Task UnbanUser(SocketGuild guild, SocketGuildUser author, SocketUser toUnban, string reason) { + if (guild.GetBanAsync(toUnban.Id) == null) { + Error(Messages.UserNotBanned, false); + return; + } + var requestOptions = Utils.GetRequestOptions($"({author}) {reason}"); await guild.RemoveBanAsync(toUnban, requestOptions); - await Utils.SilentSendAsync(channel, string.Format(Messages.UnbanResponse, toUnban.Mention, - Utils.WrapInline(reason))); - await Utils.SilentSendAsync(await guild.GetSystemChannelAsync(), notification); - await Utils.SilentSendAsync(await Utils.GetAdminLogChannel(guild), notification); - } - - public override List GetAliases() { - return new List {"unban", "разбан"}; - } - - public override int GetArgumentsAmountRequired() { - return 2; - } - - public override string GetSummary() { - return "Возвращает пользователя из бана"; + var feedback = string.Format(Messages.FeedbackUserUnbanned, toUnban.Mention, Utils.WrapInline(reason)); + Success(feedback, author.Mention, false, false); + await Utils.SendFeedback(feedback, guild.Id, author.Mention, true); } } diff --git a/Boyfriend/Commands/UnmuteCommand.cs b/Boyfriend/Commands/UnmuteCommand.cs index 9710d5a..e4af83b 100644 --- a/Boyfriend/Commands/UnmuteCommand.cs +++ b/Boyfriend/Commands/UnmuteCommand.cs @@ -1,71 +1,78 @@ using Discord; using Discord.Commands; - -// ReSharper disable UnusedType.Global -// ReSharper disable UnusedMember.Global -// ReSharper disable ClassNeverInstantiated.Global +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) { - await UnmuteMember(context.Guild, context.Channel as ITextChannel, context.Guild.GetUser(context.User.Id), - await Utils.ParseMember(context.Guild, args[0]), Utils.JoinString(args, 1)); + var author = (SocketGuildUser) context.User; + + var permissionCheckResponse = CommandHandler.HasPermission(ref author, GuildPermission.ModerateMembers); + if (permissionCheckResponse != "") { + 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 != "") { + Error(interactionCheckResponse, true); + return; + } + + var reason = Utils.JoinString(ref args, 1); + await UnmuteMember(context.Guild, author, toUnmute, reason); } - public static async Task UnmuteMember(IGuild guild, ITextChannel? channel, IGuildUser author, IGuildUser toUnmute, + public static async Task UnmuteMember(SocketGuild guild, SocketGuildUser author, SocketGuildUser toUnmute, string reason) { - await CommandHandler.CheckPermissions(author, GuildPermission.ModerateMembers, GuildPermission.ManageRoles); - await CommandHandler.CheckInteractions(author, toUnmute); - var authorMention = author.Mention; - var config = Boyfriend.GetGuildConfig(guild); - var notification = string.Format(Messages.MemberUnmuted, authorMention, toUnmute.Mention, - Utils.WrapInline(reason)); - var requestOptions = Utils.GetRequestOptions($"({Utils.GetNameAndDiscrim(author)}) {reason}"); - var role = Utils.GetMuteRole(guild); + var requestOptions = Utils.GetRequestOptions($"({author}) {reason}"); + var role = Utils.GetMuteRole(ref guild); if (role != null) { - if (toUnmute.RoleIds.All(x => x != role.Id)) { - var rolesRemoved = config.RolesRemovedOnMute; - - await toUnmute.AddRolesAsync(rolesRemoved![toUnmute.Id]); - rolesRemoved.Remove(toUnmute.Id); - await config.Save(); - throw new ApplicationException(Messages.RolesReturned); + var muted = false; + foreach (var x in toUnmute.Roles) { + if (x != role) continue; + muted = true; + break; } - if (toUnmute.RoleIds.All(x => x != role.Id)) - throw new ApplicationException(Messages.MemberNotMuted); + var rolesRemoved = Boyfriend.GetRemovedRoles(guild.Id); - await toUnmute.RemoveRoleAsync(role, requestOptions); - if (config.RolesRemovedOnMute!.ContainsKey(toUnmute.Id)) { - await toUnmute.AddRolesAsync(config.RolesRemovedOnMute[toUnmute.Id]); - config.RolesRemovedOnMute.Remove(toUnmute.Id); - await config.Save(); + if (rolesRemoved.ContainsKey(toUnmute.Id)) { + await toUnmute.AddRolesAsync(rolesRemoved[toUnmute.Id]); + rolesRemoved.Remove(toUnmute.Id); + CommandHandler.ConfigWriteScheduled = true; + } + + if (muted) { + await toUnmute.RemoveRoleAsync(role, requestOptions); + } else { + Error(Messages.MemberNotMuted, false); + return; } } else { - if (toUnmute.TimedOutUntil == null || toUnmute.TimedOutUntil.Value.ToUnixTimeMilliseconds() - < DateTimeOffset.Now.ToUnixTimeMilliseconds()) - throw new ApplicationException(Messages.MemberNotMuted); + if (toUnmute.TimedOutUntil == null || toUnmute.TimedOutUntil.Value.ToUnixTimeMilliseconds() < + DateTimeOffset.Now.ToUnixTimeMilliseconds()) { + Error(Messages.MemberNotMuted, false); + return; + } await toUnmute.RemoveTimeOutAsync(); } - await Utils.SilentSendAsync(channel, string.Format(Messages.UnmuteResponse, toUnmute.Mention, - Utils.WrapInline(reason))); - await Utils.SilentSendAsync(await guild.GetSystemChannelAsync(), notification); - await Utils.SilentSendAsync(await Utils.GetAdminLogChannel(guild), notification); - } - - public override List GetAliases() { - return new List {"unmute", "размут"}; - } - - public override int GetArgumentsAmountRequired() { - return 2; - } - - public override string GetSummary() { - return "Снимает мут с участника"; + var feedback = string.Format(Messages.FeedbackMemberUnmuted, toUnmute.Mention, Utils.WrapInline(reason)); + Success(feedback, author.Mention, false, false); + await Utils.SendFeedback(feedback, guild.Id, author.Mention, true); } } diff --git a/Boyfriend/EventHandler.cs b/Boyfriend/EventHandler.cs index bb484c8..8109395 100644 --- a/Boyfriend/EventHandler.cs +++ b/Boyfriend/EventHandler.cs @@ -1,5 +1,4 @@ -using System.Globalization; -using Boyfriend.Commands; +using Boyfriend.Commands; using Discord; using Discord.Commands; using Discord.WebSocket; @@ -15,61 +14,75 @@ public class EventHandler { _client.MessageReceived += MessageReceivedEvent; _client.MessageUpdated += MessageUpdatedEvent; _client.UserJoined += UserJoinedEvent; + _client.GuildScheduledEventCreated += ScheduledEventCreatedEvent; + _client.GuildScheduledEventCancelled += ScheduledEventCancelledEvent; + _client.GuildScheduledEventStarted += ScheduledEventStartedEvent; + _client.GuildScheduledEventCompleted += ScheduledEventCompletedEvent; } private static async Task ReadyEvent() { - await Boyfriend.SetupGuildConfigs(); - - var i = new Random().Next(3); + var i = Utils.Random.Next(3); foreach (var guild in Boyfriend.Client.Guilds) { - var config = Boyfriend.GetGuildConfig(guild); - var channel = guild.GetTextChannel(config.BotLogChannel.GetValueOrDefault(0)); - Messages.Culture = new CultureInfo(config.Lang!); + var config = Boyfriend.GetGuildConfig(guild.Id); + var channel = guild.GetTextChannel(Convert.ToUInt64(config["BotLogChannel"])); + Utils.SetCurrentLanguage(guild.Id); - if (!config.ReceiveStartupMessages.GetValueOrDefault(true) || channel == null) continue; - await channel.SendMessageAsync(string.Format(Messages.Ready, Utils.GetBeep(config.Lang!, i))); + if (config["ReceiveStartupMessages"] != "true" || channel == null) continue; + await channel.SendMessageAsync(string.Format(Messages.Ready, Utils.GetBeep(i))); } } private static async Task MessageDeletedEvent(Cacheable message, Cacheable channel) { var msg = message.Value; + if (msg is null or ISystemMessage || msg.Author.IsBot) return; - var toSend = msg == null ? string.Format(Messages.UncachedMessageDeleted, Utils.MentionChannel(channel.Id)) - : string.Format(Messages.CachedMessageDeleted, msg.Author.Mention) + - $"{Utils.MentionChannel(channel.Id)}: {Environment.NewLine}{Utils.Wrap(msg.Content)}"; - await Utils.SilentSendAsync(await Utils.GetAdminLogChannel(Boyfriend.FindGuild(channel.Value)), toSend); + var guild = Boyfriend.FindGuild(channel.Value.Id); + + Utils.SetCurrentLanguage(guild.Id); + + var auditLogEntry = (await guild.GetAuditLogsAsync(1).FlattenAsync()).First(); + var mention = auditLogEntry.User.Mention; + if (auditLogEntry.Action != ActionType.MessageDeleted || + DateTimeOffset.Now.Subtract(auditLogEntry.CreatedAt).TotalMilliseconds > 500 || + auditLogEntry.User.IsBot) mention = msg.Author.Mention; + + await Utils.SendFeedback( + string.Format(Messages.CachedMessageDeleted, msg.Author.Mention, Utils.MentionChannel(channel.Id), + Utils.WrapAsNeeded(msg.CleanContent)), guild.Id, mention); } private static async Task MessageReceivedEvent(SocketMessage messageParam) { if (messageParam is not SocketUserMessage message) return; - var argPos = 0; - var user = (IGuildUser) message.Author; + var user = (SocketGuildUser) message.Author; var guild = user.Guild; - var guildConfig = Boyfriend.GetGuildConfig(guild); + var guildConfig = Boyfriend.GetGuildConfig(guild.Id); + + Utils.SetCurrentLanguage(guild.Id); + + if ((message.MentionedUsers.Count > 3 || message.MentionedRoles.Count > 2) && + !user.GuildPermissions.MentionEveryone) { + await BanCommand.BanUser(guild, guild.CurrentUser, user, TimeSpan.FromMilliseconds(-1), + Messages.AutobanReason); + return; + } + + var argPos = 0; var prev = ""; var prevFailsafe = ""; var prevs = await message.Channel.GetMessagesAsync(3).FlattenAsync(); var prevsArray = prevs as IMessage[] ?? prevs.ToArray(); - Messages.Culture = new CultureInfo(guildConfig.Lang!); - - if ((message.MentionedUsers.Count > 3 || message.MentionedRoles.Count > 2) && - !user.GuildPermissions.MentionEveryone) - await BanCommand.BanUser(guild, null, await guild.GetCurrentUserAsync(), user, - TimeSpan.FromMilliseconds(-1), Messages.AutobanReason); - - try { + if (prevsArray.Length >= 3) { prev = prevsArray[1].Content; prevFailsafe = prevsArray[2].Content; - } catch (IndexOutOfRangeException) {} + } - if (!(message.HasStringPrefix(guildConfig.Prefix, ref argPos) || - message.HasMentionPrefix(Boyfriend.Client.CurrentUser, ref argPos)) || - user == await guild.GetCurrentUserAsync() || - user.IsBot && (message.Content.Contains(prev) || message.Content.Contains(prevFailsafe))) + if (!(message.HasStringPrefix(guildConfig["Prefix"], ref argPos) || + message.HasMentionPrefix(Boyfriend.Client.CurrentUser, ref argPos)) || user == guild.CurrentUser || + (user.IsBot && (message.Content.Contains(prev) || message.Content.Contains(prevFailsafe)))) return; await CommandHandler.HandleCommand(message); @@ -78,27 +91,89 @@ public class EventHandler { private static async Task MessageUpdatedEvent(Cacheable messageCached, SocketMessage messageSocket, ISocketMessageChannel channel) { var msg = messageCached.Value; - var nl = Environment.NewLine; - if (msg != null && msg.Content == messageSocket.Content) return; + if (msg is null or ISystemMessage || msg.CleanContent == messageSocket.CleanContent || msg.Author.IsBot) return; - var toSend = msg == null - ? string.Format(Messages.UncachedMessageEdited, messageSocket.Author.Mention, - Utils.MentionChannel(channel.Id)) + Utils.Wrap(messageSocket.Content) : string.Format( - Messages.CachedMessageEdited, msg.Author.Mention, Utils.MentionChannel(channel.Id), nl, nl, - Utils.Wrap(msg.Content), nl, nl, Utils.Wrap(messageSocket.Content)); - await Utils.SilentSendAsync(await Utils.GetAdminLogChannel(Boyfriend.FindGuild(channel)), toSend); + var guildId = Boyfriend.FindGuild(channel.Id).Id; + + Utils.SetCurrentLanguage(guildId); + + await Utils.SendFeedback( + string.Format(Messages.CachedMessageEdited, Utils.MentionChannel(channel.Id), + Utils.WrapAsNeeded(msg.CleanContent), Utils.WrapAsNeeded(messageSocket.Content)), guildId, + msg.Author.Mention); } private static async Task UserJoinedEvent(SocketGuildUser user) { var guild = user.Guild; - var config = Boyfriend.GetGuildConfig(guild); + var config = Boyfriend.GetGuildConfig(guild.Id); - if (config.SendWelcomeMessages.GetValueOrDefault(true)) + if (config["SendWelcomeMessages"] == "true") await Utils.SilentSendAsync(guild.SystemChannel, - string.Format(config.WelcomeMessage!, user.Mention, guild.Name)); + string.Format(config["WelcomeMessage"], user.Mention, guild.Name)); - if (config.DefaultRole != 0) - await user.AddRoleAsync(Utils.ParseRole(guild, config.DefaultRole.ToString()!)); + if (config["StarterRole"] != "0") + await user.AddRoleAsync(ulong.Parse(config["StarterRole"])); } -} \ No newline at end of file + + private static async Task ScheduledEventCreatedEvent(SocketGuildEvent scheduledEvent) { + var guild = scheduledEvent.Guild; + var eventConfig = Boyfriend.GetGuildConfig(guild.Id); + var channel = guild.GetTextChannel(Convert.ToUInt64(eventConfig["EventCreatedChannel"])); + + if (channel != null) { + var roleMention = ""; + var role = guild.GetRole(Convert.ToUInt64(eventConfig["EventNotifyReceiverRole"])); + if (role != null) + roleMention = $"{role.Mention} "; + + var location = Utils.WrapInline(scheduledEvent.Location) ?? Utils.MentionChannel(scheduledEvent.Channel.Id); + + await Utils.SilentSendAsync(channel, + string.Format(Messages.EventCreated, "\n", roleMention, scheduledEvent.Creator.Mention, + Utils.WrapInline(scheduledEvent.Name), location, + scheduledEvent.StartTime.ToUnixTimeSeconds().ToString(), Utils.Wrap(scheduledEvent.Description)), + true); + } + } + + private static async Task ScheduledEventCancelledEvent(SocketGuildEvent scheduledEvent) { + var guild = scheduledEvent.Guild; + var eventConfig = Boyfriend.GetGuildConfig(guild.Id); + var channel = guild.GetTextChannel(Convert.ToUInt64(eventConfig["EventCancelledChannel"])); + if (channel != null) + await channel.SendMessageAsync(string.Format(Messages.EventCancelled, Utils.WrapInline(scheduledEvent.Name), + eventConfig["FrowningFace"] == "true" ? $" {Messages.SettingsFrowningFace}" : "")); + } + + private static async Task ScheduledEventStartedEvent(SocketGuildEvent scheduledEvent) { + var guild = scheduledEvent.Guild; + var eventConfig = Boyfriend.GetGuildConfig(guild.Id); + var channel = guild.GetTextChannel(Convert.ToUInt64(eventConfig["EventStartedChannel"])); + + if (channel != null) { + var receivers = eventConfig["EventStartedReceivers"]; + var role = guild.GetRole(Convert.ToUInt64(eventConfig["EventNotifyReceiverRole"])); + var mentions = Boyfriend.StringBuilder; + + if (receivers.Contains("role") && role != null) mentions.Append($"{role.Mention} "); + if (receivers.Contains("users") || receivers.Contains("interested")) + foreach (var user in await scheduledEvent.GetUsersAsync(15)) + mentions = mentions.Append($"{user.Mention} "); + + await channel.SendMessageAsync(string.Format(Messages.EventStarted, mentions, + Utils.WrapInline(scheduledEvent.Name), + Utils.WrapInline(scheduledEvent.Location) ?? Utils.MentionChannel(scheduledEvent.Channel.Id))); + mentions.Clear(); + } + } + + private static async Task ScheduledEventCompletedEvent(SocketGuildEvent scheduledEvent) { + var guild = scheduledEvent.Guild; + var eventConfig = Boyfriend.GetGuildConfig(guild.Id); + var channel = guild.GetTextChannel(Convert.ToUInt64(eventConfig["EventCompletedChannel"])); + if (channel != null) + await channel.SendMessageAsync(string.Format(Messages.EventCompleted, Utils.WrapInline(scheduledEvent.Name), + Utils.WrapInline(scheduledEvent.StartTime.Subtract(DateTimeOffset.Now).Negate().ToString()))); + } +} diff --git a/Boyfriend/GuildConfig.cs b/Boyfriend/GuildConfig.cs deleted file mode 100644 index 59d25a3..0000000 --- a/Boyfriend/GuildConfig.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Globalization; -using Newtonsoft.Json; -// ReSharper disable MemberCanBePrivate.Global - -namespace Boyfriend; - -public class GuildConfig { - - public GuildConfig(ulong id) { - Id = id; - Validate(); - } - public ulong? Id { get; } - public string? Lang { get; set; } - public string? Prefix { get; set; } - - public bool? RemoveRolesOnMute { get; set; } - public bool? UseSystemChannel { get; set; } - public bool? SendWelcomeMessages { get; set; } - public bool? ReceiveStartupMessages { get; set; } - - public string? WelcomeMessage { get; set; } - - public ulong? DefaultRole { get; set; } - public ulong? MuteRole { get; set; } - public ulong? AdminLogChannel { get; set; } - public ulong? BotLogChannel { get; set; } - - public Dictionary>? RolesRemovedOnMute { get; private set; } - - public void Validate() { - if (Id == null) throw new Exception("Something went horribly, horribly wrong"); - - Lang ??= "ru"; - Messages.Culture = new CultureInfo(Lang); - Prefix ??= "!"; - RemoveRolesOnMute ??= false; - UseSystemChannel ??= true; - SendWelcomeMessages ??= true; - ReceiveStartupMessages ??= true; - WelcomeMessage ??= Messages.DefaultWelcomeMessage; - DefaultRole ??= 0; - MuteRole ??= 0; - AdminLogChannel ??= 0; - BotLogChannel ??= 0; - RolesRemovedOnMute ??= new Dictionary>(); - } - - public async Task Save() { - Validate(); - RolesRemovedOnMute!.TrimExcess(); - - await File.WriteAllTextAsync("config_" + Id + ".json", JsonConvert.SerializeObject(this)); - } -} diff --git a/Boyfriend/Messages.Designer.cs b/Boyfriend/Messages.Designer.cs index 5f6d7f5..31866d9 100644 --- a/Boyfriend/Messages.Designer.cs +++ b/Boyfriend/Messages.Designer.cs @@ -69,15 +69,6 @@ namespace Boyfriend { } } - /// - /// Looks up a localized string similar to :white_check_mark: Successfully banned {0} for {1}. - /// - internal static string BanResponse { - get { - return ResourceManager.GetString("BanResponse", resourceCulture); - } - } - /// /// Looks up a localized string similar to Bah! . /// @@ -106,7 +97,7 @@ namespace Boyfriend { } /// - /// Looks up a localized string similar to Deleted message from {0} in channel . + /// Looks up a localized string similar to Deleted message from {0} in channel {1}: {2}. /// internal static string CachedMessageDeleted { get { @@ -115,7 +106,7 @@ namespace Boyfriend { } /// - /// Looks up a localized string similar to Message edited from {0} in channel {1}.{2}Before:{3}{4}{5}After:{6}{7}. + /// Looks up a localized string similar to Edited message in channel {0}: {1} -> {2}. /// internal static string CachedMessageEdited { get { @@ -169,7 +160,88 @@ namespace Boyfriend { } /// - /// Looks up a localized string similar to Command help:{0}. + /// Looks up a localized string similar to Bans a user. + /// + internal static string CommandDescriptionBan { + get { + return ResourceManager.GetString("CommandDescriptionBan", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deletes a specified amount of messages in this channel. + /// + internal static string CommandDescriptionClear { + get { + return ResourceManager.GetString("CommandDescriptionClear", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows this message. + /// + internal static string CommandDescriptionHelp { + get { + return ResourceManager.GetString("CommandDescriptionHelp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Kicks a member. + /// + internal static string CommandDescriptionKick { + get { + return ResourceManager.GetString("CommandDescriptionKick", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mutes a member. + /// + internal static string CommandDescriptionMute { + get { + return ResourceManager.GetString("CommandDescriptionMute", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows latency to Discord servers (not counting local processing time). + /// + internal static string CommandDescriptionPing { + get { + return ResourceManager.GetString("CommandDescriptionPing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Allows you to change certain preferences for this guild. + /// + internal static string CommandDescriptionSettings { + get { + return ResourceManager.GetString("CommandDescriptionSettings", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unbans a user. + /// + internal static string CommandDescriptionUnban { + get { + return ResourceManager.GetString("CommandDescriptionUnban", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unmutes a member. + /// + internal static string CommandDescriptionUnmute { + get { + return ResourceManager.GetString("CommandDescriptionUnmute", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Command help:. /// internal static string CommandHelp { get { @@ -205,7 +277,7 @@ namespace Boyfriend { } /// - /// Looks up a localized string similar to Current settings:{0}. + /// Looks up a localized string similar to Current settings:. /// internal static string CurrentSettings { get { @@ -213,105 +285,6 @@ namespace Boyfriend { } } - /// - /// Looks up a localized string similar to Admin log channel (`adminLogChannel`): {0}{1}. - /// - internal static string CurrentSettingsAdminLogChannel { - get { - return ResourceManager.GetString("CurrentSettingsAdminLogChannel", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Bot log channel (`botLogChannel`): {0}. - /// - internal static string CurrentSettingsBotLogChannel { - get { - return ResourceManager.GetString("CurrentSettingsBotLogChannel", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Default role (`defaultRole`): {0}{1}. - /// - internal static string CurrentSettingsDefaultRole { - get { - return ResourceManager.GetString("CurrentSettingsDefaultRole", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Language (`lang`): `{0}`{1}. - /// - internal static string CurrentSettingsLang { - get { - return ResourceManager.GetString("CurrentSettingsLang", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Mute role (`muteRole`): {0}{1}. - /// - internal static string CurrentSettingsMuteRole { - get { - return ResourceManager.GetString("CurrentSettingsMuteRole", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Prefix (`prefix`): `{0}`{1}. - /// - internal static string CurrentSettingsPrefix { - get { - return ResourceManager.GetString("CurrentSettingsPrefix", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Receive startup messages (`receiveStartupMessages`): {0}{1}. - /// - internal static string CurrentSettingsReceiveStartupMessages { - get { - return ResourceManager.GetString("CurrentSettingsReceiveStartupMessages", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Remove roles on mute (`removeRolesOnMute`): {0}{1}. - /// - internal static string CurrentSettingsRemoveRoles { - get { - return ResourceManager.GetString("CurrentSettingsRemoveRoles", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Send welcome messages (`sendWelcomeMessages`): {0}{1}. - /// - internal static string CurrentSettingsSendWelcomeMessages { - get { - return ResourceManager.GetString("CurrentSettingsSendWelcomeMessages", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use system channel for notifications (`useSystemChannel`): {0}{1}. - /// - internal static string CurrentSettingsUseSystemChannel { - get { - return ResourceManager.GetString("CurrentSettingsUseSystemChannel", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Welcome message: `{0}`{1}. - /// - internal static string CurrentSettingsWelcomeMessage { - get { - return ResourceManager.GetString("CurrentSettingsWelcomeMessage", resourceCulture); - } - } - /// /// Looks up a localized string similar to {0}, welcome to {1}. /// @@ -339,6 +312,123 @@ namespace Boyfriend { } } + /// + /// Looks up a localized string similar to Event {0} is cancelled!{1}. + /// + internal static string EventCancelled { + get { + return ResourceManager.GetString("EventCancelled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Event {0} has completed! Duration: {1}. + /// + internal static string EventCompleted { + get { + return ResourceManager.GetString("EventCompleted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {1}{2} created event {3}! It will take place in {4} and will start <t:{5}:R>!{0}{6}. + /// + internal static string EventCreated { + get { + return ResourceManager.GetString("EventCreated", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0}Event {1} is starting at {2}!. + /// + internal static string EventStarted { + get { + return ResourceManager.GetString("EventStarted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ever. + /// + internal static string Ever { + get { + return ResourceManager.GetString("Ever", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to *[{0}: {1}]*. + /// + internal static string FeedbackFormat { + get { + return ResourceManager.GetString("FeedbackFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Kicked {0}: {1}. + /// + internal static string FeedbackMemberKicked { + get { + return ResourceManager.GetString("FeedbackMemberKicked", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Muted {0} for{1}: {2}. + /// + internal static string FeedbackMemberMuted { + get { + return ResourceManager.GetString("FeedbackMemberMuted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unmuted {0}: {1}. + /// + internal static string FeedbackMemberUnmuted { + get { + return ResourceManager.GetString("FeedbackMemberUnmuted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deleted {0} messages in {1}. + /// + internal static string FeedbackMessagesCleared { + get { + return ResourceManager.GetString("FeedbackMessagesCleared", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value of setting `{0}` is now set to {1}. + /// + internal static string FeedbackSettingsUpdated { + get { + return ResourceManager.GetString("FeedbackSettingsUpdated", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Banned {0} for{1}: {2}. + /// + internal static string FeedbackUserBanned { + get { + return ResourceManager.GetString("FeedbackUserBanned", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unbanned {0}: {1}. + /// + internal static string FeedbackUserUnbanned { + get { + return ResourceManager.GetString("FeedbackUserUnbanned", resourceCulture); + } + } + /// /// Looks up a localized string similar to Members are in different guilds!. /// @@ -420,15 +510,6 @@ namespace Boyfriend { } } - /// - /// Looks up a localized string similar to :white_check_mark: Successfully kicked {0} for {1}. - /// - internal static string KickResponse { - get { - return ResourceManager.GetString("KickResponse", resourceCulture); - } - } - /// /// Looks up a localized string similar to Language not supported!. /// @@ -447,24 +528,6 @@ namespace Boyfriend { } } - /// - /// Looks up a localized string similar to {0} kicked {1} for {2}. - /// - internal static string MemberKicked { - get { - return ResourceManager.GetString("MemberKicked", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} muted {1} for {2}{3}. - /// - internal static string MemberMuted { - get { - return ResourceManager.GetString("MemberMuted", resourceCulture); - } - } - /// /// Looks up a localized string similar to Member not muted!. /// @@ -483,15 +546,6 @@ namespace Boyfriend { } } - /// - /// Looks up a localized string similar to {0} deleted {1} messages in channel {2}. - /// - internal static string MessagesDeleted { - get { - return ResourceManager.GetString("MessagesDeleted", resourceCulture); - } - } - /// /// Looks up a localized string similar to ms. /// @@ -501,15 +555,6 @@ namespace Boyfriend { } } - /// - /// Looks up a localized string similar to :white_check_mark: Successfully muted {0} for {1}. - /// - internal static string MuteResponse { - get { - return ResourceManager.GetString("MuteResponse", resourceCulture); - } - } - /// /// Looks up a localized string similar to No. /// @@ -537,15 +582,6 @@ namespace Boyfriend { } } - /// - /// Looks up a localized string similar to {0}This punishment will expire <t:{1}:R>. - /// - internal static string PunishmentExpiresIn { - get { - return ResourceManager.GetString("PunishmentExpiresIn", resourceCulture); - } - } - /// /// Looks up a localized string similar to {0}I'm ready! (C#). /// @@ -592,25 +628,178 @@ namespace Boyfriend { } /// - /// Looks up a localized string similar to Settings successfully updated. + /// Looks up a localized string similar to Not specified. /// - internal static string SettingsUpdated { + internal static string SettingNotDefined { get { - return ResourceManager.GetString("SettingsUpdated", resourceCulture); + return ResourceManager.GetString("SettingNotDefined", resourceCulture); } } /// - /// Looks up a localized string similar to :white_check_mark: Successfully unbanned {0} for {1}. + /// Looks up a localized string similar to Admin log channel. /// - internal static string UnbanResponse { + internal static string SettingsAdminLogChannel { get { - return ResourceManager.GetString("UnbanResponse", resourceCulture); + return ResourceManager.GetString("SettingsAdminLogChannel", resourceCulture); } } /// - /// Looks up a localized string similar to Deleted message in {0}, but I forgot what was there. + /// Looks up a localized string similar to Bot log channel. + /// + internal static string SettingsBotLogChannel { + get { + return ResourceManager.GetString("SettingsBotLogChannel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Channel for event cancellation notifications. + /// + internal static string SettingsEventCancelledChannel { + get { + return ResourceManager.GetString("SettingsEventCancelledChannel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Channel for event completion notifications. + /// + internal static string SettingsEventCompletedChannel { + get { + return ResourceManager.GetString("SettingsEventCompletedChannel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Channel for event creation notifications. + /// + internal static string SettingsEventCreatedChannel { + get { + return ResourceManager.GetString("SettingsEventCreatedChannel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Role for event creation notifications. + /// + internal static string SettingsEventNotifyReceiverRole { + get { + return ResourceManager.GetString("SettingsEventNotifyReceiverRole", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Channel for event start notifications. + /// + internal static string SettingsEventStartedChannel { + get { + return ResourceManager.GetString("SettingsEventStartedChannel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Event start notifications receivers. + /// + internal static string SettingsEventStartedReceivers { + get { + return ResourceManager.GetString("SettingsEventStartedReceivers", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to :(. + /// + internal static string SettingsFrowningFace { + get { + return ResourceManager.GetString("SettingsFrowningFace", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Language. + /// + internal static string SettingsLang { + get { + return ResourceManager.GetString("SettingsLang", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mute role. + /// + internal static string SettingsMuteRole { + get { + return ResourceManager.GetString("SettingsMuteRole", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Nothing changed! `{0}` is already set to {1}. + /// + internal static string SettingsNothingChanged { + get { + return ResourceManager.GetString("SettingsNothingChanged", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Prefix. + /// + internal static string SettingsPrefix { + get { + return ResourceManager.GetString("SettingsPrefix", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Receive startup messages. + /// + internal static string SettingsReceiveStartupMessages { + get { + return ResourceManager.GetString("SettingsReceiveStartupMessages", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Remove roles on mute. + /// + internal static string SettingsRemoveRolesOnMute { + get { + return ResourceManager.GetString("SettingsRemoveRolesOnMute", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Send welcome messages. + /// + internal static string SettingsSendWelcomeMessages { + get { + return ResourceManager.GetString("SettingsSendWelcomeMessages", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Starter role. + /// + internal static string SettingsStarterRole { + get { + return ResourceManager.GetString("SettingsStarterRole", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Welcome message. + /// + internal static string SettingsWelcomeMessage { + get { + return ResourceManager.GetString("SettingsWelcomeMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Message deleted in {0}, but I forgot what was there. /// internal static string UncachedMessageDeleted { get { @@ -619,7 +808,7 @@ namespace Boyfriend { } /// - /// Looks up a localized string similar to Message edited from {0} in channel {1}, but I forgot what was there before the edit: . + /// Looks up a localized string similar to Message edited from {0} in channel {1}, but I forgot what was there before the edit: {2}. /// internal static string UncachedMessageEdited { get { @@ -628,20 +817,11 @@ namespace Boyfriend { } /// - /// Looks up a localized string similar to :white_check_mark: Successfully unmuted {0} for {1}. + /// Looks up a localized string similar to That user doesn't exist!. /// - internal static string UnmuteResponse { + internal static string UserDoesntExist { get { - return ResourceManager.GetString("UnmuteResponse", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} banned {1} for {2}{3}. - /// - internal static string UserBanned { - get { - return ResourceManager.GetString("UserBanned", resourceCulture); + return ResourceManager.GetString("UserDoesntExist", resourceCulture); } } diff --git a/Boyfriend/Messages.resx b/Boyfriend/Messages.resx index 6f8ac2e..1f8a6e8 100644 --- a/Boyfriend/Messages.resx +++ b/Boyfriend/Messages.resx @@ -31,19 +31,19 @@ {0}I'm ready! (C#) - Deleted message in {0}, but I forgot what was there + Message deleted in {0}, but I forgot what was there - Deleted message from {0} in channel + 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: + Message edited from {0} in channel {1}, but I forgot what was there before the edit: {2} - Message edited from {0} in channel {1}.{2}Before:{3}{4}{5}After:{6}{7} + Edited message in channel {0}: {1} -> {2} {0}, welcome to {1} @@ -84,9 +84,6 @@ You were banned by {0} in guild {1} for {2} - - {0} banned {1} for {2}{3} - Punishment expired @@ -96,21 +93,12 @@ Too many messages specified! - - {0} deleted {1} messages in channel {2} - - Command help:{0} + Command help: You were kicked by {0} in guild {1} for {2} - - {0} kicked {1} for {2} - - - {0} muted {1} for {2}{3} - ms @@ -124,41 +112,35 @@ Not specified - Current settings:{0} + Current settings: - - Language (`lang`): `{0}`{1} + + Language - - Prefix (`prefix`): `{0}`{1} + + Prefix - - Remove roles on mute (`removeRolesOnMute`): {0}{1} + + Remove roles on mute - - Use system channel for notifications (`useSystemChannel`): {0}{1} + + Send welcome messages - - Send welcome messages (`sendWelcomeMessages`): {0}{1} + + Starter role - - Default role (`defaultRole`): {0}{1} + + Mute role - - Mute role (`muteRole`): {0}{1} + + Admin log channel - - Admin log channel (`adminLogChannel`): {0}{1} - - - Bot log channel (`botLogChannel`): {0} + + Bot log channel Language not supported! - - Settings successfully updated - Yes @@ -180,8 +162,8 @@ {0} unbanned {1} for {2} - - Welcome message: `{0}`{1} + + Welcome message Not enough arguments! Needed: {0}, provided: {1} @@ -189,32 +171,17 @@ Invalid message amount specified! - - :white_check_mark: Successfully banned {0} for {1} - - - :white_check_mark: Successfully kicked {0} for {1} + + Banned {0} for{1}: {2} The specified user is not a member of this server! - - :white_check_mark: Successfully muted {0} for {1} - - - :white_check_mark: Successfully unbanned {0} for {1} - - - :white_check_mark: Successfully unmuted {0} for {1} - That setting doesn't exist! - - Receive startup messages (`receiveStartupMessages`): {0}{1} - - - {0}This punishment will expire <t:{1}:R> + + Receive startup messages Invalid setting value specified! @@ -237,4 +204,97 @@ I cannot use time-outs on other bots! Try to set a mute role in settings - \ No newline at end of file + + {1}{2} created event {3}! It will take place in {4} and will start <t:{5}:R>!{0}{6} + + + Role for event creation notifications + + + Channel for event creation notifications + + + Channel for event start notifications + + + Event start notifications receivers + + + {0}Event {1} is starting at {2}! + + + :( + + + Event {0} is cancelled!{1} + + + Channel for event cancellation notifications + + + Channel for event completion notifications + + + Event {0} has completed! Duration: {1} + + + That user doesn't exist! + + + *[{0}: {1}]* + + + ever + + + Deleted {0} messages in {1} + + + Kicked {0}: {1} + + + Muted {0} for{1}: {2} + + + Unbanned {0}: {1} + + + Unmuted {0}: {1} + + + Nothing changed! `{0}` is already set to {1} + + + Not specified + + + Value of setting `{0}` is now set to {1} + + + Bans a user + + + Deletes a specified amount of messages in this channel + + + Shows this message + + + Kicks a member + + + Mutes a member + + + Shows latency to Discord servers (not counting local processing time) + + + Allows you to change certain preferences for this guild + + + Unbans a user + + + Unmutes a member + + diff --git a/Boyfriend/Messages.ru.resx b/Boyfriend/Messages.ru.resx index 0aad39e..e5eb732 100644 --- a/Boyfriend/Messages.ru.resx +++ b/Boyfriend/Messages.ru.resx @@ -25,16 +25,16 @@ Удалено сообщение в канале {0}, но я забыл что там было - Удалено сообщение от {0} в канале + Удалено сообщение от {0} в канале {1}: {2} Слишком много упоминаний в одном сообщении - Отредактировано сообщение от {0} в канале {1}, но я забыл что там было до редактирования: + Отредактировано сообщение от {0} в канале {1}, но я забыл что там было до редактирования: {2} - Отредактировано сообщение от {0} в канале {1}.{2}До:{3}{4}{5}После:{6}{7} + Отредактировано сообщение в канале {0}: {1} -> {2} {0}, добро пожаловать на сервер {1} @@ -75,9 +75,6 @@ Тебя забанил {0} на сервере {1} за {2} - - {0} банит {1} за {2}{3} - Время наказания истекло @@ -87,21 +84,12 @@ Указано слишком много сообщений! - - {0} удаляет {1} сообщений в канале {2} - - Справка по командам:{0} + Справка по командам: Тебя кикнул {0} на сервере {1} за {2} - - {0} выгоняет {1} за {2} - - - {0} глушит {1} за {2}{3} - мс @@ -115,41 +103,32 @@ Не указана - Текущие настройки:{0} + Текущие настройки: - - Язык (`lang`): `{0}`{1} + + Язык - - Префикс (`prefix`): `{0}`{1} + + Префикс - - Удалять роли при муте (`removeRolesOnMute`): {0}{1} + + Удалять роли при муте - - Использовать канал системных сообщений для уведомлений (`useSystemChannel`): {0}{1} + + Отправлять приветствия - - Отправлять приветствия (`sendWelcomeMessages`): {0}{1} + + Роль мута - - Стандартная роль (`defaultRole`): {0}{1} + + Канал админ-уведомлений - - Роль мута (`muteRole`): {0}{1} - - - Канал админ-уведомлений (`adminLogChannel`): {0}{1} - - - Канал бот-уведомлений (`botLogChannel`): {0} + + Канал бот-уведомлений Язык не поддерживается! - - Настройки успешно обновлены! - Да @@ -171,8 +150,8 @@ {0} возвращает из бана {1} за {2} - - Приветствие: `{0}`{1} + + Приветствие Недостаточно аргументов! Требуется: {0}, указано: {1} @@ -180,32 +159,17 @@ Указано неверное количество сообщений! - - :white_check_mark: Успешно забанен {0} за {1} - - - :white_check_mark: Успешно выгнан {0} за {1} + + Забанен {0} на{1}: {2} Указанный пользователь не является участником этого сервера! - - :white_check_mark: Успешно заглушен {0} за {1} - - - :white_check_mark: Успешно возвращён из бана {0} за {1} - - - :white_check_mark: Успешно возвращён из мута {0} за {1} - Такая настройка не существует! - - Получать сообщения о запуске (`receiveStartupMessages`): {0}{1} - - - {0}Это наказание истечёт <t:{1}:R> + + Получать сообщения о запуске Указано недействительное значение для настройки! @@ -228,4 +192,100 @@ Я не могу использовать тайм-ауты на других ботах! Попробуй указать роль мута в настройках - \ No newline at end of file + + Начальная роль + + + {1}{2} создал событие {3}! Оно пройдёт в {4} и начнётся <t:{5}:R>!{0}{6} + + + Роль для уведомлений о создании событий + + + Канал для уведомлений о создании событий + + + Канал для уведомлений о начале событий + + + Получатели уведомлений о начале событий + + + {0}Событие {1} начинается в {2}! + + + :( + + + Событие {0} отменено!{1} + + + Канал для уведомлений о отмене событий + + + Канал для уведомлений о завершении событий + + + Событие {0} завершено! Продолжительность: {1} + + + Такого пользователя не существует! + + + *[{0}: {1}]* + + + всегда + + + Удалено {0} сообщений в {1} + + + Выгнан {0}: {1} + + + Заглушен {0} на{1}: {2} + + + Возвращён из бана {0}: {1} + + + Разглушен {0}: {1} + + + Ничего не изменилось! Значение настройки `{0}` уже {1} + + + Не указано + + + Значение настройки `{0}` теперь установлено на {1} + + + Банит пользователя + + + Удаляет указанное количество сообщений в этом канале + + + Показывает эту справку + + + Выгоняет участника + + + Глушит участника + + + Показывает задержку до серверов Discord (не считая времени на локальные вычисления) + + + Позволяет менять некоторые настройки под этот сервер + + + Возвращает пользователя из бана + + + Разглушает участника + + diff --git a/Boyfriend/Utils.cs b/Boyfriend/Utils.cs index 8795c82..32e3bfa 100644 --- a/Boyfriend/Utils.cs +++ b/Boyfriend/Utils.cs @@ -1,11 +1,16 @@ using System.Globalization; +using System.Reflection; using System.Text.RegularExpressions; using Discord; using Discord.Net; +using Discord.WebSocket; +using Humanizer; +using Humanizer.Localisation; namespace Boyfriend; public static class Utils { + private static readonly string[] Formats = { "%d'd'%h'h'%m'm'%s's'", "%d'd'%h'h'%m'm'", "%d'd'%h'h'%s's'", "%d'd'%h'h'", "%d'd'%m'm'%s's'", "%d'd'%m'm'", "%d'd'%s's'", "%d'd'", "%h'h'%m'm'%s's'", "%h'h'%m'm'", "%h'h'%s's'", "%h'h'", "%m'm'%s's'", "%m'm'", "%s's'", @@ -14,65 +19,60 @@ public static class Utils { "%d'д'%s'с'", "%d'д'", "%h'ч'%m'м'%s'с'", "%h'ч'%m'м'", "%h'ч'%s'с'", "%h'ч'", "%m'м'%s'с'", "%m'м'", "%s'с'" }; - public static string GetBeep(string cultureInfo, int i = -1) { - Messages.Culture = new CultureInfo(cultureInfo); + public static readonly Random Random = new(); + private static readonly Dictionary ReflectionMessageCache = new(); + private static readonly Dictionary CultureInfoCache = new() { + {"ru", new CultureInfo("ru-RU")}, + {"en", new CultureInfo("en-US")} + }; + private static readonly Dictionary MuteRoleCache = new(); - var beeps = new[] {Messages.Beep1, Messages.Beep2, Messages.Beep3}; - return beeps[i < 0 ? new Random().Next(3) : i]; + private static readonly AllowedMentions AllowRoles = new() { + AllowedTypes = AllowedMentionTypes.Roles + }; + + public static string GetBeep(int i = -1) { + return GetMessage($"Beep{(i < 0 ? Random.Next(3) + 1 : ++i)}"); } - public static async Task GetAdminLogChannel(IGuild guild) { - var adminLogChannel = await ParseChannelNullable(Boyfriend.GetGuildConfig(guild).AdminLogChannel.ToString()!); - return adminLogChannel as ITextChannel; + public static SocketTextChannel? GetAdminLogChannel(ulong id) { + return Boyfriend.Client.GetGuild(id) + .GetTextChannel(ParseMention(Boyfriend.GetGuildConfig(id)["AdminLogChannel"])); } - public static string Wrap(string original) { + public static string Wrap(string? original) { + if (original == null) return ""; var toReturn = original.Replace("```", "ˋˋˋ"); return $"```{toReturn}{(toReturn.EndsWith("`") || toReturn.Trim().Equals("") ? " " : "")}```"; } - public static string WrapInline(string original) { - return $"`{original.Replace("`", "ˋ")}`"; + public static string? WrapInline(string? original) { + return original == null ? null : $"`{original.Replace("`", "ˋ")}`"; + } + + public static string? WrapAsNeeded(string? original) { + if (original == null) return null; + return original.Contains('\n') ? Wrap(original) : WrapInline(original); } public static string MentionChannel(ulong id) { return $"<#{id}>"; } - private static ulong ParseMention(string mention) { - return Convert.ToUInt64(Regex.Replace(mention, "[^0-9]", "")); + public static ulong ParseMention(string mention) { + return ulong.TryParse(Regex.Replace(mention, "[^0-9]", ""), out var id) ? id : 0; } - private static ulong? ParseMentionNullable(string mention) { - try { - return ParseMention(mention) == 0 ? throw new FormatException() : ParseMention(mention); - } catch (FormatException) { - return null; - } + public static SocketUser? ParseUser(string mention) { + var user = Boyfriend.Client.GetUser(ParseMention(mention)); + return user; } - public static async Task ParseUser(string mention) { - var user = Boyfriend.Client.GetUserAsync(ParseMention(mention)); - return await user; + public static SocketGuildUser? ParseMember(SocketGuild guild, string mention) { + return guild.GetUser(ParseMention(mention)); } - public static async Task ParseMember(IGuild guild, string mention) { - return await guild.GetUserAsync(ParseMention(mention)); - } - - private static async Task ParseChannel(string mention) { - return await Boyfriend.Client.GetChannelAsync(ParseMention(mention)); - } - - private static async Task ParseChannelNullable(string mention) { - return ParseMentionNullable(mention) == null ? null : await ParseChannel(mention); - } - - public static IRole? ParseRole(IGuild guild, string mention) { - return guild.GetRole(ParseMention(mention)); - } - - public static async Task SendDirectMessage(IUser user, string toSend) { + public static async Task SendDirectMessage(SocketUser user, string toSend) { try { await user.SendMessageAsync(toSend); } catch (HttpException e) { @@ -81,33 +81,74 @@ public static class Utils { } } - public static IRole? GetMuteRole(IGuild guild) { - var role = guild.Roles.FirstOrDefault(x => x.Id == Boyfriend.GetGuildConfig(guild).MuteRole); + public static SocketRole? GetMuteRole(ref SocketGuild guild) { + var id = ulong.Parse(Boyfriend.GetGuildConfig(guild.Id)["MuteRole"]); + if (MuteRoleCache.ContainsKey(id)) return MuteRoleCache[id]; + SocketRole? role = null; + foreach (var x in guild.Roles) { + if (x.Id != id) continue; + role = x; + MuteRoleCache.Add(id, role); + break; + } return role; } - public static async Task SilentSendAsync(ITextChannel? channel, string text) { - if (channel == null) return; + public static async Task SilentSendAsync(SocketTextChannel? channel, string text, bool allowRoles = false) { + if (channel == null || text.Length is 0 or > 2000) return; - try { - await channel.SendMessageAsync(text, false, null, null, AllowedMentions.None); - } catch (ArgumentException) {} + await channel.SendMessageAsync(text, false, null, null, allowRoles ? AllowRoles : AllowedMentions.None); } - public static TimeSpan GetTimeSpan(string from) { - return TimeSpan.ParseExact(from.ToLowerInvariant(), Formats, CultureInfo.InvariantCulture); + public static TimeSpan? GetTimeSpan(ref string from) { + if (TimeSpan.TryParseExact(from.ToLowerInvariant(), Formats, CultureInfo.InvariantCulture, out var timeSpan)) + return timeSpan; + return null; } - public static string JoinString(string[] args, int startIndex) { + public static string JoinString(ref string[] args, int startIndex) { return string.Join(" ", args, startIndex, args.Length - startIndex); } - public static string GetNameAndDiscrim(IUser user) { - return $"{user.Username}#{user.Discriminator}"; - } - public static RequestOptions GetRequestOptions(string reason) { var options = RequestOptions.Default; options.AuditLogReason = reason; return options; } -} \ No newline at end of file + + public static string GetMessage(string name) { + var propertyName = name; + name = $"{Messages.Culture}/{name}"; + if (ReflectionMessageCache.ContainsKey(name)) return ReflectionMessageCache[name]; + + var toReturn = + typeof(Messages).GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Static)?.GetValue(null) + ?.ToString()! ?? throw new Exception($"Could not find localized property: {name}"); + ReflectionMessageCache.Add(name, toReturn); + return toReturn; + } + + public static async Task SendFeedback(string feedback, ulong guildId, string mention, bool sendPublic = false) { + var adminChannel = GetAdminLogChannel(guildId); + var systemChannel = Boyfriend.Client.GetGuild(guildId).SystemChannel; + var toSend = string.Format(Messages.FeedbackFormat, mention, feedback); + if (adminChannel != null) + await SilentSendAsync(adminChannel, toSend); + if (sendPublic && systemChannel != null) + 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) { + return span.TotalSeconds > 0 ? $" {span.Humanize(minUnit: TimeUnit.Second, culture: Messages.Culture)}" + : Messages.Ever; + } + + public static void SetCurrentLanguage(ulong guildId) { + Messages.Culture = CultureInfoCache[Boyfriend.GetGuildConfig(guildId)["Lang"]]; + } +} diff --git a/UpgradeLog.htm b/UpgradeLog.htm new file mode 100644 index 0000000..5c4398b Binary files /dev/null and b/UpgradeLog.htm differ