diff --git a/Boyfriend/Boyfriend.cs b/Boyfriend/Boyfriend.cs index 1e2f489..bc44f26 100644 --- a/Boyfriend/Boyfriend.cs +++ b/Boyfriend/Boyfriend.cs @@ -16,7 +16,6 @@ public static class Boyfriend { GatewayIntents = (GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent | GatewayIntents.GuildMembers) & ~GatewayIntents.GuildInvites, - AlwaysDownloadUsers = true, AlwaysResolveStickers = false, AlwaysDownloadDefaultStickers = false, LargeThreshold = 500 @@ -112,6 +111,7 @@ public static class Boyfriend { private static async Task TickGuildAsync(SocketGuild guild) { var data = GuildData.Get(guild); var config = data.Preferences; + var saveData = false; _ = int.TryParse(config["EventEarlyNotificationOffset"], out var offset); foreach (var schEvent in guild.Events) if (config["AutoStartEvents"] is "true" && DateTimeOffset.Now >= schEvent.StartTime) { @@ -140,22 +140,30 @@ public static class Boyfriend { if (DateTimeOffset.Now >= mData.BannedUntil) _ = guild.RemoveBanAsync(mData.Id); if (mData.IsInGuild) { - if (DateTimeOffset.Now >= mData.MutedUntil) + if (DateTimeOffset.Now >= mData.MutedUntil) { await Utils.UnmuteMemberAsync(data, Client.CurrentUser.ToString(), guild.GetUser(mData.Id), Messages.PunishmentExpired); + saveData = true; + } - foreach (var reminder in mData.Reminders.Where(rem => DateTimeOffset.Now >= rem.RemindAt)) { - var channel = guild.GetTextChannel(reminder.ReminderChannel); - if (channel is null) { - await Utils.SendDirectMessage(Client.GetUser(mData.Id), reminder.ReminderText); - continue; + for (var i = mData.Reminders.Count - 1; i >= 0; i--) { + var reminder = mData.Reminders[i]; + if (DateTimeOffset.Now >= reminder.RemindAt) { + var channel = guild.GetTextChannel(reminder.ReminderChannel); + if (channel is null) { + await Utils.SendDirectMessage(Client.GetUser(mData.Id), reminder.ReminderText); + continue; + } + + await channel.SendMessageAsync($"<@{mData.Id}> {Utils.Wrap(reminder.ReminderText)}"); + mData.Reminders.RemoveAt(i); + + saveData = true; } - - await channel.SendMessageAsync($"<@{mData.Id}> {Utils.Wrap(reminder.ReminderText)}"); - - mData.Reminders.Remove(reminder); } } } + + if (saveData) data.Save(true).Wait(); } } diff --git a/Boyfriend/CommandProcessor.cs b/Boyfriend/CommandProcessor.cs index 698e908..09a49ea 100644 --- a/Boyfriend/CommandProcessor.cs +++ b/Boyfriend/CommandProcessor.cs @@ -167,7 +167,7 @@ public sealed class CommandProcessor { return Context.Guild.GetUser(id); } - public SocketGuildUser? GetMember(string[] args, string[] cleanArgs, int index, string? argument) { + public SocketGuildUser? GetMember(string[] args, int index, string? argument) { if (index >= args.Length) { Utils.SafeAppendToBuilder(_stackedReplyMessage, $"{ReplyEmojis.MissingArgument} {Messages.MissingMember}", Context.Message); @@ -177,7 +177,7 @@ public sealed class CommandProcessor { var member = Context.Guild.GetUser(Utils.ParseMention(args[index])); if (member is null && argument is not null) Utils.SafeAppendToBuilder(_stackedReplyMessage, - $"{ReplyEmojis.InvalidArgument} {string.Format(Messages.InvalidMember, Utils.Wrap(cleanArgs[index]))}", + $"{ReplyEmojis.InvalidArgument} {Messages.InvalidMember}", Context.Message); return member; } diff --git a/Boyfriend/Commands/BanCommand.cs b/Boyfriend/Commands/BanCommand.cs index e5ab6e2..b16e91b 100644 --- a/Boyfriend/Commands/BanCommand.cs +++ b/Boyfriend/Commands/BanCommand.cs @@ -35,6 +35,8 @@ public sealed class BanCommand : ICommand { = duration.TotalSeconds < 1 ? DateTimeOffset.MaxValue : DateTimeOffset.Now.Add(duration); memberData.Roles.Clear(); + cmd.ConfigWriteScheduled = true; + var feedback = string.Format(Messages.FeedbackUserBanned, $"<@{toBan.Item1.ToString()}>", Utils.GetHumanizedTimeOffset(duration), Utils.Wrap(reason)); cmd.Reply(feedback, ReplyEmojis.Banned); diff --git a/Boyfriend/Commands/ClearCommand.cs b/Boyfriend/Commands/ClearCommand.cs index fbe17fe..141cc9a 100644 --- a/Boyfriend/Commands/ClearCommand.cs +++ b/Boyfriend/Commands/ClearCommand.cs @@ -19,7 +19,7 @@ public sealed class ClearCommand : ICommand { var user = (SocketGuildUser)cmd.Context.User; await channel.DeleteMessagesAsync(messages, Utils.GetRequestOptions(user.ToString()!)); - cmd.Audit(string.Format(Messages.FeedbackMessagesCleared, (toDelete + 1).ToString())); + cmd.Audit(string.Format(Messages.FeedbackMessagesCleared, (toDelete + 1).ToString(), + Utils.MentionChannel(channel.Id))); } } - diff --git a/Boyfriend/Commands/KickCommand.cs b/Boyfriend/Commands/KickCommand.cs index fb09449..d1fd1ba 100644 --- a/Boyfriend/Commands/KickCommand.cs +++ b/Boyfriend/Commands/KickCommand.cs @@ -8,7 +8,7 @@ public sealed class KickCommand : ICommand { public string[] Aliases { get; } = { "kick", "кик", "выгнать" }; public async Task RunAsync(CommandProcessor cmd, string[] args, string[] cleanArgs) { - var toKick = cmd.GetMember(args, cleanArgs, 0, "ToKick"); + var toKick = cmd.GetMember(args, 0, "ToKick"); if (toKick is null || !cmd.HasPermission(GuildPermission.KickMembers)) return; if (cmd.CanInteractWith(toKick, "Kick")) @@ -24,6 +24,7 @@ public sealed class KickCommand : ICommand { Utils.Wrap(reason))); GuildData.Get(cmd.Context.Guild).MemberData[toKick.Id].Roles.Clear(); + cmd.ConfigWriteScheduled = true; await toKick.KickAsync(guildKickMessage); var format = string.Format(Messages.FeedbackMemberKicked, toKick.Mention, Utils.Wrap(reason)); diff --git a/Boyfriend/Commands/MuteCommand.cs b/Boyfriend/Commands/MuteCommand.cs index c704212..98c7e98 100644 --- a/Boyfriend/Commands/MuteCommand.cs +++ b/Boyfriend/Commands/MuteCommand.cs @@ -8,7 +8,7 @@ public sealed class MuteCommand : ICommand { public string[] Aliases { get; } = { "mute", "timeout", "заглушить", "мут" }; public async Task RunAsync(CommandProcessor cmd, string[] args, string[] cleanArgs) { - var toMute = cmd.GetMember(args, cleanArgs, 0, "ToMute"); + var toMute = cmd.GetMember(args, 0, "ToMute"); if (toMute is null) return; var duration = CommandProcessor.GetTimeSpan(args, 1); @@ -40,8 +40,6 @@ public sealed class MuteCommand : ICommand { await toMute.RemoveRolesAsync(toMute.Roles, requestOptions); await toMute.AddRoleAsync(role, requestOptions); - - data.MemberData[toMute.Id].MutedUntil = DateTimeOffset.Now.Add(duration); } else { if (!hasDuration || duration.TotalDays > 28) { cmd.Reply(Messages.DurationRequiredForTimeOuts, ReplyEmojis.Error); @@ -56,6 +54,9 @@ public sealed class MuteCommand : ICommand { await toMute.SetTimeOutAsync(duration, requestOptions); } + data.MemberData[toMute.Id].MutedUntil = DateTimeOffset.Now.Add(duration); + cmd.ConfigWriteScheduled = true; + var feedback = string.Format(Messages.FeedbackMemberMuted, toMute.Mention, Utils.GetHumanizedTimeOffset(duration), Utils.Wrap(reason)); diff --git a/Boyfriend/Commands/RemindCommand.cs b/Boyfriend/Commands/RemindCommand.cs index 8802a2c..24b1a08 100644 --- a/Boyfriend/Commands/RemindCommand.cs +++ b/Boyfriend/Commands/RemindCommand.cs @@ -6,6 +6,7 @@ public sealed class RemindCommand : ICommand { public string[] Aliases { get; } = { "remind", "reminder", "remindme", "напомни", "напомнить", "напоминание" }; public Task RunAsync(CommandProcessor cmd, string[] args, string[] cleanArgs) { + // TODO: actually make this good var remindIn = CommandProcessor.GetTimeSpan(args, 0); var reminderText = cmd.GetRemaining(cleanArgs, 1, "ReminderText"); if (reminderText is not null) @@ -15,6 +16,8 @@ public sealed class RemindCommand : ICommand { ReminderChannel = cmd.Context.Channel.Id }); + cmd.ConfigWriteScheduled = true; + return Task.CompletedTask; } } diff --git a/Boyfriend/Commands/UnmuteCommand.cs b/Boyfriend/Commands/UnmuteCommand.cs index 7a42460..1468966 100644 --- a/Boyfriend/Commands/UnmuteCommand.cs +++ b/Boyfriend/Commands/UnmuteCommand.cs @@ -10,7 +10,7 @@ public sealed class UnmuteCommand : ICommand { public async Task RunAsync(CommandProcessor cmd, string[] args, string[] cleanArgs) { if (!cmd.HasPermission(GuildPermission.ModerateMembers)) return; - var toUnmute = cmd.GetMember(args, cleanArgs, 0, "ToUnmute"); + var toUnmute = cmd.GetMember(args, 0, "ToUnmute"); if (toUnmute is null) return; var reason = cmd.GetRemaining(args, 1, "UnmuteReason"); if (reason is not null && cmd.CanInteractWith(toUnmute, "Unmute")) @@ -27,6 +27,8 @@ public sealed class UnmuteCommand : ICommand { return; } + cmd.ConfigWriteScheduled = true; + var feedback = string.Format(Messages.FeedbackMemberUnmuted, toUnmute.Mention, Utils.Wrap(reason)); cmd.Reply(feedback, ReplyEmojis.Unmuted); cmd.Audit(feedback); diff --git a/Boyfriend/Data/GuildData.cs b/Boyfriend/Data/GuildData.cs index d8b5325..9d00525 100644 --- a/Boyfriend/Data/GuildData.cs +++ b/Boyfriend/Data/GuildData.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using System.Text.Json; using Discord.WebSocket; @@ -24,10 +25,17 @@ public record GuildData { { "AutoStartEvents", "false" } }; - public static readonly Dictionary GuildDataDictionary = new(); + public static readonly ConcurrentDictionary GuildDataDictionary = new(); + + private static readonly JsonSerializerOptions Options = new() { + IncludeFields = true, + WriteIndented = true + }; private readonly string _configurationFile; + private readonly ulong _id; + public readonly List EarlyNotifications = new(); public readonly Dictionary MemberData; @@ -38,17 +46,16 @@ public record GuildData { private SocketTextChannel? _cachedPrivateFeedbackChannel; private SocketTextChannel? _cachedPublicFeedbackChannel; - private ulong _id; - [SuppressMessage("Performance", "CA1853:Unnecessary call to \'Dictionary.ContainsKey(key)\'")] // https://github.com/dotnet/roslyn-analyzers/issues/6377 private GuildData(SocketGuild guild) { + _id = guild.Id; var idString = $"{_id}"; var memberDataDir = $"{_id}/MemberData"; _configurationFile = $"{_id}/Configuration.json"; if (!Directory.Exists(idString)) Directory.CreateDirectory(idString); if (!Directory.Exists(memberDataDir)) Directory.CreateDirectory(memberDataDir); - if (!File.Exists(_configurationFile)) File.Create(_configurationFile).Dispose(); + if (!File.Exists(_configurationFile)) File.WriteAllText(_configurationFile, "{}"); Preferences = JsonSerializer.Deserialize>(File.ReadAllText(_configurationFile)) ?? new Dictionary(); @@ -64,16 +71,17 @@ public record GuildData { MemberData = new Dictionary(); foreach (var data in Directory.GetFiles(memberDataDir)) { var deserialised - = JsonSerializer.Deserialize(File.ReadAllText($"{_id}/MemberData/{data}.json")); + = JsonSerializer.Deserialize(File.ReadAllText(data), Options); MemberData.Add(deserialised!.Id, deserialised); } - - foreach (var member in guild.Users) { + guild.DownloadUsersAsync().Wait(); + foreach (var member in guild.Users.Where(user => !user.IsBot)) { if (MemberData.TryGetValue(member.Id, out var memberData)) { if (!memberData.IsInGuild && DateTimeOffset.Now.ToUnixTimeSeconds() - - Math.Max(memberData.LeftAt.Last().ToUnixTimeSeconds(), memberData.BannedUntil.ToUnixTimeSeconds()) > + Math.Max(memberData.LeftAt.Last().ToUnixTimeSeconds(), + memberData.BannedUntil?.ToUnixTimeSeconds() ?? 0) > 60 * 60 * 24 * 30) { File.Delete($"{_id}/MemberData/{memberData.Id}.json"); MemberData.Remove(memberData.Id); @@ -117,10 +125,8 @@ public record GuildData { public static GuildData Get(SocketGuild guild) { if (GuildDataDictionary.TryGetValue(guild.Id, out var stored)) return stored; - var newData = new GuildData(guild) { - _id = guild.Id - }; - GuildDataDictionary.Add(guild.Id, newData); + var newData = new GuildData(guild); + while (!GuildDataDictionary.ContainsKey(guild.Id)) GuildDataDictionary.TryAdd(guild.Id, newData); return newData; } @@ -131,6 +137,6 @@ public record GuildData { if (saveMemberData) foreach (var data in MemberData.Values) await File.WriteAllTextAsync($"{_id}/MemberData/{data.Id}.json", - JsonSerializer.Serialize(data)); + JsonSerializer.Serialize(data, Options)); } } diff --git a/Boyfriend/Data/MemberData.cs b/Boyfriend/Data/MemberData.cs index 3c3f6b1..8bf8391 100644 --- a/Boyfriend/Data/MemberData.cs +++ b/Boyfriend/Data/MemberData.cs @@ -3,12 +3,12 @@ namespace Boyfriend.Data; public record MemberData { - public DateTimeOffset BannedUntil; + public DateTimeOffset? BannedUntil; public ulong Id; public bool IsInGuild; public List JoinedAt; public List LeftAt; - public DateTimeOffset MutedUntil; + public DateTimeOffset? MutedUntil; public List Reminders; public List Roles; @@ -19,7 +19,5 @@ public record MemberData { LeftAt = new List(); Roles = user.RoleIds.ToList(); Reminders = new List(); - MutedUntil = DateTimeOffset.MinValue; - BannedUntil = DateTimeOffset.MinValue; } } diff --git a/Boyfriend/EventHandler.cs b/Boyfriend/EventHandler.cs index 7badd28..0a5ca2a 100644 --- a/Boyfriend/EventHandler.cs +++ b/Boyfriend/EventHandler.cs @@ -34,8 +34,9 @@ public static class EventHandler { var i = Random.Shared.Next(3); foreach (var guild in Client.Guilds) { - var config = GuildData.Get(guild).Preferences; - var channel = guild.GetTextChannel(Utils.ParseMention(config["BotLogChannel"])); + var data = GuildData.Get(guild); + var config = data.Preferences; + var channel = data.PrivateFeedbackChannel; Utils.SetCurrentLanguage(guild); if (config["ReceiveStartupMessages"] is not "true" || channel is null) continue; @@ -101,12 +102,13 @@ public static class EventHandler { } private static async Task UserJoinedEvent(SocketGuildUser user) { + if (user.IsBot) return; var guild = user.Guild; var data = GuildData.Get(guild); var config = data.Preferences; Utils.SetCurrentLanguage(guild); - if (config["SendWelcomeMessages"] is "true") + if (config["SendWelcomeMessages"] is "true" && data.PublicFeedbackChannel is not null) await Utils.SilentSendAsync(data.PublicFeedbackChannel, string.Format(config["WelcomeMessage"] is "default" ? Messages.DefaultWelcomeMessage @@ -117,7 +119,7 @@ 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 = DateTimeOffset.MinValue; + memberData.BannedUntil = null; if (memberData.LeftAt.Count > 0) { if (memberData.JoinedAt.Contains(user.JoinedAt!.Value)) throw new UnreachableException(); @@ -127,9 +129,6 @@ public static class EventHandler { if (memberData.MutedUntil < DateTimeOffset.Now) { if (data.MuteRole is not null) await user.AddRoleAsync(data.MuteRole); - else - await user.SetTimeOutAsync(DateTimeOffset.Now - memberData.MutedUntil); - if (config["RemoveRolesOnMute"] is "false" && config["ReturnRolesOnRejoin"] is "true") await user.AddRolesAsync(memberData.Roles); } else if (config["ReturnRolesOnRejoin"] is "true") { await user.AddRolesAsync(memberData.Roles); } diff --git a/Boyfriend/Messages.Designer.cs b/Boyfriend/Messages.Designer.cs index 24d4da9..430abc2 100644 --- a/Boyfriend/Messages.Designer.cs +++ b/Boyfriend/Messages.Designer.cs @@ -284,6 +284,15 @@ namespace Boyfriend { } } + /// + /// Looks up a localized string similar to Adds a reminder. + /// + internal static string CommandDescriptionRemind { + get { + return ResourceManager.GetString("CommandDescriptionRemind", resourceCulture); + } + } + /// /// Looks up a localized string similar to Allows you to change certain preferences for this guild. /// @@ -492,7 +501,7 @@ namespace Boyfriend { } /// - /// Looks up a localized string similar to You need to specify a guild member instead of {0}!. + /// Looks up a localized string similar to You did not specify a member of this guild!. /// internal static string InvalidMember { get { @@ -608,6 +617,15 @@ namespace Boyfriend { } } + /// + /// Looks up a localized string similar to You need to specify reminder text!. + /// + internal static string MissingReminderText { + get { + return ResourceManager.GetString("MissingReminderText", resourceCulture); + } + } + /// /// Looks up a localized string similar to You need to specify a setting to change!. /// @@ -717,11 +735,11 @@ namespace Boyfriend { } /// - /// Looks up a localized string similar to Bot log channel. + /// Looks up a localized string similar to Automatically start scheduled events. /// - internal static string SettingsBotLogChannel { + internal static string SettingsAutoStartEvents { get { - return ResourceManager.GetString("SettingsBotLogChannel", resourceCulture); + return ResourceManager.GetString("SettingsAutoStartEvents", resourceCulture); } } @@ -806,6 +824,24 @@ namespace Boyfriend { } } + /// + /// Looks up a localized string similar to Channel for private notifications. + /// + internal static string SettingsPrivateFeedbackChannel { + get { + return ResourceManager.GetString("SettingsPrivateFeedbackChannel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Channel for public notifications. + /// + internal static string SettingsPublicFeedbackChannel { + get { + return ResourceManager.GetString("SettingsPublicFeedbackChannel", resourceCulture); + } + } + /// /// Looks up a localized string similar to Receive startup messages. /// @@ -824,6 +860,15 @@ namespace Boyfriend { } } + /// + /// Looks up a localized string similar to Return roles on rejoin. + /// + internal static string SettingsReturnRolesOnRejoin { + get { + return ResourceManager.GetString("SettingsReturnRolesOnRejoin", resourceCulture); + } + } + /// /// Looks up a localized string similar to Send welcome messages. /// @@ -1068,7 +1113,7 @@ namespace Boyfriend { } /// - /// Looks up a localized string similar to You were banned by {0} in guild {1} for {2}. + /// Looks up a localized string similar to You were banned by {0} in guild `{1}` for {2}. /// internal static string YouWereBanned { get { @@ -1077,7 +1122,7 @@ namespace Boyfriend { } /// - /// Looks up a localized string similar to You were kicked by {0} in guild {1} for {2}. + /// Looks up a localized string similar to You were kicked by {0} in guild `{1}` for {2}. /// internal static string YouWereKicked { get { diff --git a/Boyfriend/Messages.resx b/Boyfriend/Messages.resx index b70fbfc..d8b7393 100644 --- a/Boyfriend/Messages.resx +++ b/Boyfriend/Messages.resx @@ -135,43 +135,43 @@ Bop! - + Beep! - + I do not have permission to execute this command! - + You do not have permission to execute this command! - - You were banned by {0} in guild {1} for {2} - - + + You were banned by {0} in guild `{1}` for {2} + + Punishment expired - + You specified less than {0} messages! - + You specified more than {0} messages! - + Command help: - - You were kicked by {0} in guild {1} for {2} - - + + You were kicked by {0} in guild `{1}` for {2} + + ms - + Member is already muted! - + Not specified - + Not specified @@ -192,10 +192,7 @@ Mute role - - Bot log channel - - + Language not supported! Supported languages: @@ -327,28 +324,28 @@ You need to specify an integer from {0} to {1}! - + You need to specify a user! - + You need to specify a user instead of {0}! - + You need to specify a guild member! - - You need to specify a guild member instead of {0}! - - + + You did not specify a member of this guild! + + You cannot ban users from this guild! - + You cannot manage messages in this guild! - + You cannot kick members from this guild! - + You cannot moderate members in this guild! @@ -459,4 +456,22 @@ Starter role + + Adds a reminder + + + Channel for public notifications + + + Channel for private notifications + + + Return roles on rejoin + + + Automatically start scheduled events + + + You need to specify reminder text! + diff --git a/Boyfriend/Messages.ru.resx b/Boyfriend/Messages.ru.resx index 70a3cbe..fa00246 100644 --- a/Boyfriend/Messages.ru.resx +++ b/Boyfriend/Messages.ru.resx @@ -135,43 +135,43 @@ Боп! - + Бип! - + У меня недостаточно прав для выполнения этой команды! - + У тебя недостаточно прав для выполнения этой команды! - - Тебя забанил {0} на сервере {1} за {2} - - + + Тебя забанил {0} на сервере `{1}` за {2} + + Время наказания истекло - + Указано менее {0} сообщений! - + Указано более {0} сообщений! - + Справка по командам: - - Тебя кикнул {0} на сервере {1} за {2} - - + + Тебя кикнул {0} на сервере `{1}` за {2} + + мс - + Участник уже заглушен! - + Не указан - + Не указана @@ -192,10 +192,7 @@ Роль мута - - Канал бот-уведомлений - - + Язык не поддерживается! Поддерживаемые языки: @@ -327,28 +324,28 @@ Надо указать целое число от {0} до {1}! - + Надо указать пользователя! - + Надо указать пользователя вместо {0}! - + Надо указать участника сервера! - - Надо указать участника сервера вместо {0}! - - + + Тебе надо указать участника этого сервера! + + Ты не можешь банить пользователей на этом сервере! - + Ты не можешь управлять сообщениями этого сервера! - + Ты не можешь выгонять участников с этого сервера! - + Ты не можешь модерировать участников этого сервера! @@ -459,4 +456,22 @@ Начальная роль + + Добавляет напоминание + + + Канал для публичных уведомлений + + + Канал для приватных уведомлений + + + Возвращать роли при перезаходе + + + Автоматически начинать события + + + Тебе нужно указать текст напоминания! + diff --git a/Boyfriend/Messages.tt-ru.resx b/Boyfriend/Messages.tt-ru.resx index 15e7305..189f877 100644 --- a/Boyfriend/Messages.tt-ru.resx +++ b/Boyfriend/Messages.tt-ru.resx @@ -135,43 +135,43 @@ брох! - + брух! - + у меня прав нету, сделай что нибудь. - + у тебя прав нету, твои проблемы. - - здарова, тебя крч забанил {0} на сервере {1} за {2} - - + + здарова, тебя крч забанил {0} на сервере `{1}` за {2} + + время бана закончиловсь - + ты выбрал менее {0} сообщений - + ты выбрал более {0} сообщений - + туториал по приколам: - - здарова, тебя крч кикнул {0} на сервере {1} за {2} - - + + здарова, тебя крч кикнул {0} на сервере `{1}` за {2} + + мс - + шизоид уже замучен! - + *тут ничего нет* - + *тут ничего нет* @@ -192,10 +192,7 @@ роль замученного - - канал бот-уведомлений - - + такого языка нету, ты шо, есть только такие: @@ -327,28 +324,28 @@ укажи целое число от {0} до {1} - + укажи самого шизика - + надо указать юзверя вместо {0}! - + укажи самого шизика - - укажи шизоида сервера вместо {0}! - - + + укажи шизоида сервера! + + бан - + тебе нельзя иметь власть над сообщениями шизоидов - + кик шизиков нельзя - + тебе нельзя управлять шизоидами @@ -459,4 +456,22 @@ базовое звание + + TODO + + + TODO + + + TODO + + + TODO + + + TODO + + + TODO + diff --git a/Boyfriend/Utils.cs b/Boyfriend/Utils.cs index b09f7d2..dba9573 100644 --- a/Boyfriend/Utils.cs +++ b/Boyfriend/Utils.cs @@ -147,6 +147,7 @@ public static partial class Utils { if (!toUnmute.Roles.Contains(role)) return false; await toUnmute.AddRolesAsync(data.MemberData[toUnmute.Id].Roles, requestOptions); await toUnmute.RemoveRoleAsync(role, requestOptions); + data.MemberData[toUnmute.Id].MutedUntil = null; } else { if (toUnmute.TimedOutUntil is null || toUnmute.TimedOutUntil.Value < DateTimeOffset.Now) return false;