From 4b2d98c440e3afa2cb79d458819fe508668d06cc Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 12 Jan 2023 22:00:52 +0500 Subject: [PATCH] Keep adapting code to new guild data storage... Fix #15, update InspectCode, make Dependabot use the correct label --- .github/dependabot.yml | 8 +- .github/workflows/resharper.yml | 2 +- Boyfriend/CommandProcessor.cs | 33 ++++--- Boyfriend/Commands/BanCommand.cs | 20 +++-- Boyfriend/Commands/MuteCommand.cs | 6 +- Boyfriend/Commands/RemindCommand.cs | 2 +- Boyfriend/Commands/UnmuteCommand.cs | 3 +- Boyfriend/Data/GuildData.cs | 22 ++--- Boyfriend/Data/MemberData.cs | 16 ++-- Boyfriend/Data/Reminder.cs | 2 +- Boyfriend/EventHandler.cs | 14 ++- Boyfriend/Messages.Designer.cs | 6 +- Boyfriend/Messages.resx | 130 ++++++++++++++-------------- Boyfriend/Messages.ru.resx | 3 - Boyfriend/Messages.tt-ru.resx | 3 - Boyfriend/Utils.cs | 13 +++ 16 files changed, 152 insertions(+), 131 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b3f8cdb..18ba8f9 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,7 +13,9 @@ updates: # Allow both direct and indirect updates for all packages - dependency-type: "all" assignees: - - "l1ttleO" + - "Octol1ttle" + labels: + - "type: dependencies" - package-ecosystem: "nuget" # See documentation for possible values directory: "/Boyfriend" # Location of package manifests @@ -24,4 +26,6 @@ updates: - dependency-type: "all" # Add assignees assignees: - - "l1ttleO" + - "Octol1ttle" + labels: + - "type: dependencies" diff --git a/.github/workflows/resharper.yml b/.github/workflows/resharper.yml index 8c55f0f..9a91364 100644 --- a/.github/workflows/resharper.yml +++ b/.github/workflows/resharper.yml @@ -29,7 +29,7 @@ jobs: run: dotnet restore - name: ReSharper CLI InspectCode - uses: muno92/resharper_inspectcode@1.6.0 + uses: muno92/resharper_inspectcode@1.6.6 with: solutionPath: ./Boyfriend-CSharp.sln ignoreIssueType: InvertIf diff --git a/Boyfriend/CommandProcessor.cs b/Boyfriend/CommandProcessor.cs index a14fdd8..00675f8 100644 --- a/Boyfriend/CommandProcessor.cs +++ b/Boyfriend/CommandProcessor.cs @@ -112,19 +112,30 @@ public sealed class CommandProcessor { return null; } - public SocketUser? GetUser(string[] args, string[] cleanArgs, int index, string? argument) { + public Tuple? GetUser(string[] args, string[] cleanArgs, int index) { if (index >= args.Length) { Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{ReplyEmojis.MissingArgument} {Messages.MissingUser}", Context.Message); return null; } - var user = Boyfriend.Client.GetUser(Utils.ParseMention(args[index])); - if (user is null && argument is not null) + var mention = Utils.ParseMention(args[index]); + if (mention is 0) { Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{ReplyEmojis.InvalidArgument} {string.Format(Messages.InvalidUser, Utils.Wrap(cleanArgs[index]))}", Context.Message); - return user; + return null; + } + + var exists = Utils.UserExists(mention); + if (!exists) { + Utils.SafeAppendToBuilder(_stackedReplyMessage, + $"{ReplyEmojis.Error} {string.Format(Messages.UserNotFound, Utils.Wrap(cleanArgs[index]))}", + Context.Message); + return null; + } + + return Tuple.Create(mention, (SocketUser?)Boyfriend.Client.GetUser(mention)); } public bool HasPermission(GuildPermission permission) { @@ -135,7 +146,7 @@ public sealed class CommandProcessor { return false; } - if (!Context.Guild.GetUser(Context.User.Id).GuildPermissions.Has(permission) + if (!GetMember().GuildPermissions.Has(permission) && Context.Guild.OwnerId != Context.User.Id) { Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{ReplyEmojis.NoPermission} {Utils.GetMessage($"UserCannot{permission}")}", @@ -146,8 +157,12 @@ public sealed class CommandProcessor { return true; } - public SocketGuildUser? GetMember(SocketUser user) { - return Context.Guild.GetUser(user.Id); + private SocketGuildUser GetMember() { + return GetMember(Context.User.Id)!; + } + + public SocketGuildUser? GetMember(ulong id) { + return Context.Guild.GetUser(id); } public SocketGuildUser? GetMember(string[] args, string[] cleanArgs, int index, string? argument) { @@ -165,10 +180,6 @@ public sealed class CommandProcessor { return member; } - private SocketGuildUser GetMember() { - return Context.Guild.GetUser(Context.User.Id); - } - public ulong? GetBan(string[] args, int index) { if (index >= args.Length) { Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{ReplyEmojis.MissingArgument} {Messages.MissingUser}", diff --git a/Boyfriend/Commands/BanCommand.cs b/Boyfriend/Commands/BanCommand.cs index c83b5bf..3297156 100644 --- a/Boyfriend/Commands/BanCommand.cs +++ b/Boyfriend/Commands/BanCommand.cs @@ -8,10 +8,10 @@ public sealed class BanCommand : ICommand { public string[] Aliases { get; } = { "ban", "бан" }; public async Task RunAsync(CommandProcessor cmd, string[] args, string[] cleanArgs) { - var toBan = cmd.GetUser(args, cleanArgs, 0, "ToBan"); + var toBan = cmd.GetUser(args, cleanArgs, 0); if (toBan is null || !cmd.HasPermission(GuildPermission.BanMembers)) return; - var memberToBan = cmd.GetMember(toBan); + var memberToBan = cmd.GetMember(toBan.Item1); if (memberToBan is not null && !cmd.CanInteractWith(memberToBan, "Ban")) return; var duration = CommandProcessor.GetTimeSpan(args, 1); @@ -19,21 +19,23 @@ public sealed class BanCommand : ICommand { if (reason is not null) await BanUserAsync(cmd, toBan, duration, reason); } - private static async Task BanUserAsync(CommandProcessor cmd, SocketUser toBan, TimeSpan duration, string reason) { + private static async Task BanUserAsync(CommandProcessor cmd, Tuple toBan, TimeSpan duration, + string reason) { var author = cmd.Context.User; var guild = cmd.Context.Guild; - await Utils.SendDirectMessage(toBan, - string.Format(Messages.YouWereBanned, author.Mention, guild.Name, Utils.Wrap(reason))); + if (toBan.Item2 is not null) + await Utils.SendDirectMessage(toBan.Item2, + string.Format(Messages.YouWereBanned, author.Mention, guild.Name, Utils.Wrap(reason))); var guildBanMessage = $"({author}) {reason}"; - await guild.AddBanAsync(toBan, 0, guildBanMessage); + await guild.AddBanAsync(toBan.Item1, 0, guildBanMessage); - var memberData = GuildData.FromSocketGuild(guild).MemberData[toBan.Id]; + var memberData = GuildData.FromSocketGuild(guild).MemberData[toBan.Item1]; memberData.BannedUntil - = duration.TotalSeconds < 1 ? -1 : DateTimeOffset.Now.Add(duration).ToUnixTimeSeconds(); + = duration.TotalSeconds < 1 ? DateTimeOffset.MaxValue : DateTimeOffset.Now.Add(duration); memberData.Roles.Clear(); - var feedback = string.Format(Messages.FeedbackUserBanned, toBan.Mention, + var feedback = string.Format(Messages.FeedbackUserBanned, $"<@{toBan.Item1.ToString()}>", Utils.GetHumanizedTimeOffset(duration), Utils.Wrap(reason)); cmd.Reply(feedback, ReplyEmojis.Banned); cmd.Audit(feedback); diff --git a/Boyfriend/Commands/MuteCommand.cs b/Boyfriend/Commands/MuteCommand.cs index 0fd8b02..db201aa 100644 --- a/Boyfriend/Commands/MuteCommand.cs +++ b/Boyfriend/Commands/MuteCommand.cs @@ -19,8 +19,8 @@ public sealed class MuteCommand : ICommand { if ((role is not null && toMute.Roles.Contains(role)) || (toMute.TimedOutUntil is not null - && toMute.TimedOutUntil.Value.ToUnixTimeSeconds() - > DateTimeOffset.Now.ToUnixTimeSeconds())) { + && toMute.TimedOutUntil.Value + > DateTimeOffset.Now)) { cmd.Reply(Messages.MemberAlreadyMuted, ReplyEmojis.Error); return; } @@ -41,7 +41,7 @@ public sealed class MuteCommand : ICommand { await toMute.AddRoleAsync(role, requestOptions); - data.MemberData[toMute.Id].MutedUntil = DateTimeOffset.Now.Add(duration).ToUnixTimeSeconds(); + data.MemberData[toMute.Id].MutedUntil = DateTimeOffset.Now.Add(duration); } else { if (!hasDuration || duration.TotalDays > 28) { cmd.Reply(Messages.DurationRequiredForTimeOuts, ReplyEmojis.Error); diff --git a/Boyfriend/Commands/RemindCommand.cs b/Boyfriend/Commands/RemindCommand.cs index f2cb86d..0147f28 100644 --- a/Boyfriend/Commands/RemindCommand.cs +++ b/Boyfriend/Commands/RemindCommand.cs @@ -10,7 +10,7 @@ public sealed class RemindCommand : ICommand { var reminderText = cmd.GetRemaining(args, 1, "ReminderText"); if (reminderText is not null) GuildData.FromSocketGuild(cmd.Context.Guild).MemberData[cmd.Context.User.Id].Reminders.Add(new Reminder { - RemindAt = DateTimeOffset.Now.Add(remindIn).ToUnixTimeSeconds(), + RemindAt = DateTimeOffset.Now.Add(remindIn), ReminderText = reminderText }); diff --git a/Boyfriend/Commands/UnmuteCommand.cs b/Boyfriend/Commands/UnmuteCommand.cs index 74fe6ff..b25acc8 100644 --- a/Boyfriend/Commands/UnmuteCommand.cs +++ b/Boyfriend/Commands/UnmuteCommand.cs @@ -27,8 +27,7 @@ public sealed class UnmuteCommand : ICommand { await toUnmute.AddRolesAsync(data.MemberData[toUnmute.Id].Roles, requestOptions); await toUnmute.RemoveRoleAsync(role, requestOptions); } else { - if (toUnmute.TimedOutUntil is null || toUnmute.TimedOutUntil.Value.ToUnixTimeSeconds() < - DateTimeOffset.Now.ToUnixTimeSeconds()) { + if (toUnmute.TimedOutUntil is null || toUnmute.TimedOutUntil.Value < DateTimeOffset.Now) { cmd.Reply(Messages.MemberNotMuted, ReplyEmojis.Error); return; } diff --git a/Boyfriend/Data/GuildData.cs b/Boyfriend/Data/GuildData.cs index 04cd06d..b4b4db8 100644 --- a/Boyfriend/Data/GuildData.cs +++ b/Boyfriend/Data/GuildData.cs @@ -24,7 +24,7 @@ public record GuildData { // TODO: { "AutoStartEvents", "false" } }; - private static readonly Dictionary GuildDataDictionary = new(); + public static readonly Dictionary GuildDataDictionary = new(); public readonly Dictionary MemberData; @@ -44,15 +44,12 @@ public record GuildData { = JsonSerializer.Deserialize>(File.ReadAllText($"{_id}/Configuration.json")) ?? new Dictionary(); - // ReSharper disable twice ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator if (Preferences.Keys.Count < DefaultPreferences.Keys.Count) - foreach (var key in DefaultPreferences.Keys) - if (!Preferences.ContainsKey(key)) - Preferences.Add(key, DefaultPreferences[key]); + foreach (var key in DefaultPreferences.Keys.Where(key => !Preferences.ContainsKey(key))) + Preferences.Add(key, DefaultPreferences[key]); if (Preferences.Keys.Count > DefaultPreferences.Keys.Count) - foreach (var key in Preferences.Keys) - if (!DefaultPreferences.ContainsKey(key)) - Preferences.Remove(key); + foreach (var key in Preferences.Keys.Where(key => !DefaultPreferences.ContainsKey(key))) + Preferences.Remove(key); Preferences.TrimExcess(); MemberData = new Dictionary(); @@ -67,7 +64,7 @@ public record GuildData { if (MemberData.TryGetValue(member.Id, out var memberData)) { if (!memberData.IsInGuild && DateTimeOffset.Now.ToUnixTimeSeconds() - - Math.Max(memberData.LeftAt.Last(), memberData.BannedUntil) > + Math.Max(memberData.LeftAt.Last().ToUnixTimeSeconds(), memberData.BannedUntil.ToUnixTimeSeconds()) > 60 * 60 * 24 * 30) { File.Delete($"{_id}/MemberData/{memberData.Id}.json"); MemberData.Remove(memberData.Id); @@ -83,8 +80,11 @@ public record GuildData { } public SocketRole? MuteRole { - get => _cachedMuteRole ??= Boyfriend.Client.GetGuild(_id).Roles - .Single(x => x.Id == ulong.Parse(Preferences["MuteRole"])); + get { + if (Preferences["MuteRole"] is "0") return null; + return _cachedMuteRole ??= Boyfriend.Client.GetGuild(_id).Roles + .Single(x => x.Id == ulong.Parse(Preferences["MuteRole"])); + } set => _cachedMuteRole = value; } diff --git a/Boyfriend/Data/MemberData.cs b/Boyfriend/Data/MemberData.cs index 26fc349..3c3f6b1 100644 --- a/Boyfriend/Data/MemberData.cs +++ b/Boyfriend/Data/MemberData.cs @@ -3,23 +3,23 @@ namespace Boyfriend.Data; public record MemberData { - public long BannedUntil; + public DateTimeOffset BannedUntil; public ulong Id; public bool IsInGuild; - public List JoinedAt; - public List LeftAt; - public long MutedUntil; + public List JoinedAt; + public List LeftAt; + public DateTimeOffset MutedUntil; public List Reminders; public List Roles; public MemberData(IGuildUser user) { Id = user.Id; IsInGuild = true; - JoinedAt = new List { user.JoinedAt!.Value.ToUnixTimeSeconds() }; - LeftAt = new List(); + JoinedAt = new List { user.JoinedAt!.Value }; + LeftAt = new List(); Roles = user.RoleIds.ToList(); Reminders = new List(); - MutedUntil = 0; - BannedUntil = 0; + MutedUntil = DateTimeOffset.MinValue; + BannedUntil = DateTimeOffset.MinValue; } } diff --git a/Boyfriend/Data/Reminder.cs b/Boyfriend/Data/Reminder.cs index 3b14c20..9d3d034 100644 --- a/Boyfriend/Data/Reminder.cs +++ b/Boyfriend/Data/Reminder.cs @@ -1,6 +1,6 @@ namespace Boyfriend.Data; public struct Reminder { - public long RemindAt; + public DateTimeOffset RemindAt; public string ReminderText; } diff --git a/Boyfriend/EventHandler.cs b/Boyfriend/EventHandler.cs index 500ebfa..e8108f8 100644 --- a/Boyfriend/EventHandler.cs +++ b/Boyfriend/EventHandler.cs @@ -110,19 +110,18 @@ public static class EventHandler { if (!data.MemberData.ContainsKey(user.Id)) data.MemberData.Add(user.Id, new MemberData(user)); var memberData = data.MemberData[user.Id]; memberData.IsInGuild = true; - memberData.BannedUntil = 0; + memberData.BannedUntil = DateTimeOffset.MinValue; if (memberData.LeftAt.Count > 0) { - if (memberData.JoinedAt.Contains(user.JoinedAt!.Value.ToUnixTimeSeconds())) + if (memberData.JoinedAt.Contains(user.JoinedAt!.Value)) throw new UnreachableException(); - memberData.JoinedAt.Add(user.JoinedAt!.Value.ToUnixTimeSeconds()); + memberData.JoinedAt.Add(user.JoinedAt!.Value); } - if (memberData.MutedUntil < DateTimeOffset.Now.ToUnixTimeSeconds()) { + if (memberData.MutedUntil < DateTimeOffset.Now) { if (data.MuteRole is not null) await user.AddRoleAsync(data.MuteRole); else - await user.SetTimeOutAsync( - TimeSpan.FromSeconds(DateTimeOffset.Now.ToUnixTimeSeconds() - memberData.MutedUntil)); + await user.SetTimeOutAsync(DateTimeOffset.Now - memberData.MutedUntil); if (config["RemoveRolesOnMute"] is "false" && config["ReturnRolesOnRejoin"] is "true") await user.AddRolesAsync(memberData.Roles); @@ -132,11 +131,10 @@ public static class EventHandler { private static Task UserLeftEvent(SocketGuild guild, SocketUser user) { var data = GuildData.FromSocketGuild(guild).MemberData[user.Id]; data.IsInGuild = false; - data.LeftAt.Add(DateTimeOffset.Now.ToUnixTimeSeconds()); + data.LeftAt.Add(DateTimeOffset.Now); return Task.CompletedTask; } - // TODO: store data about event (early) notifications private static async Task ScheduledEventCreatedEvent(SocketGuildEvent scheduledEvent) { var guild = scheduledEvent.Guild; var eventConfig = GuildData.FromSocketGuild(guild).Preferences; diff --git a/Boyfriend/Messages.Designer.cs b/Boyfriend/Messages.Designer.cs index 9aea677..d05442c 100644 --- a/Boyfriend/Messages.Designer.cs +++ b/Boyfriend/Messages.Designer.cs @@ -1050,11 +1050,11 @@ namespace Boyfriend { } /// - /// Looks up a localized string similar to The specified user is not a member of this server!. + /// Looks up a localized string similar to . /// - internal static string UserNotInGuild { + internal static string UserNotFound { get { - return ResourceManager.GetString("UserNotInGuild", resourceCulture); + return ResourceManager.GetString("UserNotFound", resourceCulture); } } diff --git a/Boyfriend/Messages.resx b/Boyfriend/Messages.resx index 56fea2d..93cb1ea 100644 --- a/Boyfriend/Messages.resx +++ b/Boyfriend/Messages.resx @@ -1,64 +1,64 @@  - + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + text/microsoft-resx + 2.0 + System.Resources.ResXResourceReader, System.Windows.Forms, ... + System.Resources.ResXResourceWriter, System.Windows.Forms, ... + this is my long stringthis is a comment + Blue + + [base64 mime encoded serialized .NET Framework object] + + + [base64 mime encoded string representing a byte array form of the .NET Framework object] + This is a comment + + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> @@ -225,9 +225,6 @@ Banned {0} for{1}: {2} - - The specified user is not a member of this server! - That setting doesn't exist! @@ -450,13 +447,16 @@ I cannot unmute this member! - + You cannot unmute this user! - + {0}Event {1} will start <t:{2}:R>! - + Early event start notification offset - \ No newline at end of file + + + + diff --git a/Boyfriend/Messages.ru.resx b/Boyfriend/Messages.ru.resx index e6a8836..1e18a25 100644 --- a/Boyfriend/Messages.ru.resx +++ b/Boyfriend/Messages.ru.resx @@ -222,9 +222,6 @@ Забанен {0} на{1}: {2} - - Указанный пользователь не является участником этого сервера! - Такая настройка не существует! diff --git a/Boyfriend/Messages.tt-ru.resx b/Boyfriend/Messages.tt-ru.resx index 8d57c67..9ac5be9 100644 --- a/Boyfriend/Messages.tt-ru.resx +++ b/Boyfriend/Messages.tt-ru.resx @@ -222,9 +222,6 @@ забанен {0} на{1}: {2} - - шизик не на этом сервере - такой прикол не существует diff --git a/Boyfriend/Utils.cs b/Boyfriend/Utils.cs index e3c6ce8..8aa7096 100644 --- a/Boyfriend/Utils.cs +++ b/Boyfriend/Utils.cs @@ -49,6 +49,11 @@ public static partial class Utils { return ulong.TryParse(NumbersOnlyRegex().Replace(mention, ""), out var id) ? id : 0; } + public static async Task SendDirectMessage(ulong id, string toSend) { + if (await Boyfriend.Client.GetUserAsync(id) is SocketUser user) + await SendDirectMessage(user, toSend); + } + public static async Task SendDirectMessage(SocketUser user, string toSend) { try { await user.SendMessageAsync(toSend); } catch (HttpException e) { if (e.DiscordCode is not DiscordErrorCode.CannotSendMessageToUser) throw; @@ -133,6 +138,14 @@ public static partial class Utils { .Preferences["EventNotificationChannel"])); } + public static bool UserExists(ulong id) { + return Boyfriend.Client.GetUser(id) is not null || UserInMemberData(id); + } + + private static bool UserInMemberData(ulong id) { + return GuildData.GuildDataDictionary.Values.Any(gData => gData.MemberData.Values.Any(mData => mData.Id == id)); + } + [GeneratedRegex("[^0-9]")] private static partial Regex NumbersOnlyRegex(); }