From 382add19a3ce33efccf9a7524d34d702ee682002 Mon Sep 17 00:00:00 2001 From: l1ttleO Date: Fri, 10 Dec 2021 16:25:29 +0500 Subject: [PATCH] a ton of stuff --- Boyfriend/Boyfriend.cs | 4 +- Boyfriend/Boyfriend.csproj | 2 +- Boyfriend/Boyfriend.csproj.DotSettings | 2 +- Boyfriend/Commands/BanModule.cs | 30 ++++++---- Boyfriend/Commands/ClearModule.cs | 34 +++++++++++ Boyfriend/Commands/KickModule.cs | 34 +++++++++++ Boyfriend/Commands/MuteModule.cs | 35 +++++++++++ Boyfriend/Commands/PingModule.cs | 2 +- Boyfriend/Commands/Unban.cs | 23 -------- Boyfriend/Commands/UnbanModule.cs | 29 +++++++++ Boyfriend/Commands/UnmuteModule.cs | 29 +++++++++ Boyfriend/EventHandler.cs | 82 ++++++++++++++++++-------- Boyfriend/Utils.cs | 65 ++++++++++++++++++-- 13 files changed, 304 insertions(+), 67 deletions(-) create mode 100644 Boyfriend/Commands/ClearModule.cs create mode 100644 Boyfriend/Commands/KickModule.cs create mode 100644 Boyfriend/Commands/MuteModule.cs delete mode 100644 Boyfriend/Commands/Unban.cs create mode 100644 Boyfriend/Commands/UnbanModule.cs create mode 100644 Boyfriend/Commands/UnmuteModule.cs diff --git a/Boyfriend/Boyfriend.cs b/Boyfriend/Boyfriend.cs index cea4da0..9937d0e 100644 --- a/Boyfriend/Boyfriend.cs +++ b/Boyfriend/Boyfriend.cs @@ -8,7 +8,8 @@ namespace Boyfriend; => Init().GetAwaiter().GetResult(); private static readonly DiscordSocketConfig Config = new() { - MessageCacheSize = 250 + MessageCacheSize = 250, + GatewayIntents = GatewayIntents.All }; public static readonly DiscordSocketClient Client = new(Config); @@ -18,6 +19,7 @@ namespace Boyfriend; await Client.LoginAsync(TokenType.Bot, token); await Client.StartAsync(); + await Client.SetActivityAsync(new Game("Retrospecter - Electrospasm", ActivityType.Listening)); await new EventHandler().InitEvents(); diff --git a/Boyfriend/Boyfriend.csproj b/Boyfriend/Boyfriend.csproj index 9d8eac3..9b9566e 100644 --- a/Boyfriend/Boyfriend.csproj +++ b/Boyfriend/Boyfriend.csproj @@ -14,7 +14,7 @@ - + diff --git a/Boyfriend/Boyfriend.csproj.DotSettings b/Boyfriend/Boyfriend.csproj.DotSettings index 25ce924..47a5106 100644 --- a/Boyfriend/Boyfriend.csproj.DotSettings +++ b/Boyfriend/Boyfriend.csproj.DotSettings @@ -1,3 +1,3 @@  - Yes + No \ No newline at end of file diff --git a/Boyfriend/Commands/BanModule.cs b/Boyfriend/Commands/BanModule.cs index 973059f..049c203 100644 --- a/Boyfriend/Commands/BanModule.cs +++ b/Boyfriend/Commands/BanModule.cs @@ -1,7 +1,9 @@ using Discord; using Discord.Commands; + // ReSharper disable UnusedType.Global // ReSharper disable UnusedMember.Global +// ReSharper disable ClassNeverInstantiated.Global namespace Boyfriend.Commands; @@ -10,17 +12,25 @@ public class BanModule : ModuleBase { [Command("ban")] [Summary("Банит пользователя")] [Alias("бан")] - public async Task Run(IUser toBan, TimeSpan duration, [Remainder]string reason) - => await BanUser(Context.Guild, Context.User, toBan, duration, reason); + [RequireBotPermission(GuildPermission.BanMembers)] + [RequireUserPermission(GuildPermission.BanMembers)] + public Task Run(string user, TimeSpan duration, [Remainder]string reason) { + var toBan = Utils.ParseUser(user).Result; + BanUser(Context.Guild, Context.User, toBan, duration, reason); + return Task.CompletedTask; + } - public async void BanUser(IGuild guild, IUser author, IUser toBan, TimeSpan duration, string reason = "") { + public static async void BanUser(IGuild guild, IUser author, IUser toBan, TimeSpan duration, string reason) { var authorMention = author.Mention; - await toBan.SendMessageAsync("Тебя забанил " + authorMention + " за " + reason); - await guild.AddBanAsync(toBan, 0, reason); - await guild.GetSystemChannelAsync().Result.SendMessageAsync(authorMention + " банит " + toBan.Mention + " за " - + reason); - var banTimer = new System.Timers.Timer(duration.Milliseconds); - banTimer.Elapsed += UnbanModule.UnbanUser(guild, author, toBan, "Время наказания истекло").; - banTimer.Start(); + await Utils.SendDirectMessage(toBan, $"Тебя забанил {author.Mention} на сервере {guild.Name} за `{reason}`"); + + var guildBanMessage = $"({author.Username}#{author.Discriminator}) {reason}"; + await guild.AddBanAsync(toBan, 0, guildBanMessage); + var notification = $"{authorMention} банит {toBan.Mention} за {Utils.WrapInline(reason)}"; + await Utils.SilentSendAsync(guild.GetSystemChannelAsync().Result, notification); + await Utils.SilentSendAsync(Utils.GetAdminLogChannel(), notification); + var task = new Task(() => UnbanModule.UnbanUser(guild, guild.GetCurrentUserAsync().Result, toBan, + "Время наказания истекло")); + await Utils.StartDelayed(task, duration, () => guild.GetBanAsync(toBan).Result != null); } } \ No newline at end of file diff --git a/Boyfriend/Commands/ClearModule.cs b/Boyfriend/Commands/ClearModule.cs new file mode 100644 index 0000000..f17dab0 --- /dev/null +++ b/Boyfriend/Commands/ClearModule.cs @@ -0,0 +1,34 @@ +using Discord; +using Discord.Commands; + +// ReSharper disable UnusedType.Global +// ReSharper disable UnusedMember.Global +// ReSharper disable ClassNeverInstantiated.Global + +namespace Boyfriend.Commands; + +public class ClearModule : ModuleBase { + + [Command("clear")] + [Summary("Удаляет указанное количество сообщений")] + [Alias("очистить")] + [RequireBotPermission(GuildPermission.ManageMessages)] + [RequireUserPermission(GuildPermission.ManageMessages)] + public async Task Run(int toDelete) { + if (Context.Channel is not ITextChannel channel) return; + switch (toDelete) { + case < 1: + throw new ArgumentException("toDelete is less than 1."); + case > 200: + throw new ArgumentException("toDelete is more than 200."); + default: { + var messages = await channel.GetMessagesAsync(toDelete + 1).FlattenAsync(); + await channel.DeleteMessagesAsync(messages); + await Utils.GetAdminLogChannel().SendMessageAsync( + $"{Context.User.Mention} удаляет {toDelete + 1} сообщений в канале " + + $"{Utils.MentionChannel(Context.Channel.Id)}"); + break; + } + } + } +} \ No newline at end of file diff --git a/Boyfriend/Commands/KickModule.cs b/Boyfriend/Commands/KickModule.cs new file mode 100644 index 0000000..4989557 --- /dev/null +++ b/Boyfriend/Commands/KickModule.cs @@ -0,0 +1,34 @@ +using Discord; +using Discord.Commands; + +// ReSharper disable UnusedType.Global +// ReSharper disable UnusedMember.Global +// ReSharper disable ClassNeverInstantiated.Global + +namespace Boyfriend.Commands; + +public class KickModule : ModuleBase { + + [Command("kick")] + [Summary("Выгоняет пользователя")] + [Alias("кик")] + [RequireBotPermission(GuildPermission.KickMembers)] + [RequireUserPermission(GuildPermission.KickMembers)] + public Task Run(string user, [Remainder]string reason) { + var toKick = Utils.ParseMember(Context.Guild, user).Result; + KickMember(Context.Guild, Context.User, toKick, reason); + return Task.CompletedTask; + } + + private static async void KickMember(IGuild guild, IUser author, IGuildUser toKick, string reason) { + var authorMention = author.Mention; + await Utils.SendDirectMessage(toKick, $"Тебя кикнул {authorMention} на сервере {guild.Name} за " + + $"{Utils.WrapInline(reason)}"); + + var guildKickMessage = $"({author.Username}#{author.Discriminator}) {reason}"; + await toKick.KickAsync(guildKickMessage); + var notification = $"{authorMention} выгоняет {toKick.Mention} за {Utils.WrapInline(reason)}"; + await Utils.SilentSendAsync(guild.GetSystemChannelAsync().Result, notification); + await Utils.SilentSendAsync(Utils.GetAdminLogChannel(), notification); + } +} \ No newline at end of file diff --git a/Boyfriend/Commands/MuteModule.cs b/Boyfriend/Commands/MuteModule.cs new file mode 100644 index 0000000..f95cbd3 --- /dev/null +++ b/Boyfriend/Commands/MuteModule.cs @@ -0,0 +1,35 @@ +using Discord; +using Discord.Commands; + +// ReSharper disable UnusedType.Global +// ReSharper disable UnusedMember.Global +// ReSharper disable ClassNeverInstantiated.Global + +namespace Boyfriend.Commands; + +public class MuteModule : ModuleBase { + + [Command("mute")] + [Summary("Глушит пользователя")] + [Alias("мут")] + [RequireBotPermission(GuildPermission.ManageRoles)] + [RequireUserPermission(GuildPermission.ManageMessages)] + public Task Run(string user, TimeSpan duration, [Remainder]string reason) { + var toMute = Utils.ParseMember(Context.Guild, user).Result; + MuteMember(Context.Guild, Context.User, toMute, duration, reason); + return Task.CompletedTask; + } + + private static async void MuteMember(IGuild guild, IMentionable author, IGuildUser toMute, TimeSpan duration, + string reason) { + var authorMention = author.Mention; + var role = Utils.GetMuteRole(guild); + await toMute.AddRoleAsync(role); + var notification = $"{authorMention} глушит {toMute.Mention} за {Utils.WrapInline(reason)}"; + await Utils.SilentSendAsync(guild.GetSystemChannelAsync().Result, notification); + await Utils.SilentSendAsync(Utils.GetAdminLogChannel(), notification); + var task = new Task(() => UnmuteModule.UnmuteMember(guild, guild.GetCurrentUserAsync().Result, toMute, + "Время наказания истекло")); + await Utils.StartDelayed(task, duration, () => toMute.RoleIds.Any(x => x == role.Id)); + } +} \ No newline at end of file diff --git a/Boyfriend/Commands/PingModule.cs b/Boyfriend/Commands/PingModule.cs index 39d0ad7..6c86073 100644 --- a/Boyfriend/Commands/PingModule.cs +++ b/Boyfriend/Commands/PingModule.cs @@ -10,5 +10,5 @@ public class PingModule : ModuleBase { [Summary("Измеряет время обработки REST-запроса")] [Alias("пинг")] public async Task Run() - => await ReplyAsync(Utils.GetBeep() + Boyfriend.Client.Latency + "мс"); + => await ReplyAsync($"{Utils.GetBeep()}{Boyfriend.Client.Latency}мс"); } \ No newline at end of file diff --git a/Boyfriend/Commands/Unban.cs b/Boyfriend/Commands/Unban.cs deleted file mode 100644 index 0ef4361..0000000 --- a/Boyfriend/Commands/Unban.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Discord; -using Discord.Commands; -// ReSharper disable UnusedType.Global -// ReSharper disable UnusedMember.Global - -namespace Boyfriend.Commands; - -public class UnbanModule : ModuleBase { - - [Command("unban")] - [Summary("Возвращает пользователя из бана")] - [Alias("разбан")] - public async Task Run(IUser toBan, TimeSpan duration, [Remainder]string reason) - => await UnbanUser(Context.Guild, Context.User, toBan, reason); - - public async Task UnbanUser(IGuild guild, IUser author, IUser toBan, string reason = "") { - var authorMention = author.Mention; - await toBan.SendMessageAsync("Тебя разбанил " + authorMention + " за " + reason); - await guild.RemoveBanAsync(toBan); - await guild.GetSystemChannelAsync().Result.SendMessageAsync(authorMention + " возвращает из бана " - + toBan.Mention + " за " + reason); - } -} \ No newline at end of file diff --git a/Boyfriend/Commands/UnbanModule.cs b/Boyfriend/Commands/UnbanModule.cs new file mode 100644 index 0000000..0a2e841 --- /dev/null +++ b/Boyfriend/Commands/UnbanModule.cs @@ -0,0 +1,29 @@ +using Discord; +using Discord.Commands; +// ReSharper disable UnusedType.Global +// ReSharper disable UnusedMember.Global +// ReSharper disable ClassNeverInstantiated.Global + +namespace Boyfriend.Commands; + +public class UnbanModule : ModuleBase { + + [Command("unban")] + [Summary("Возвращает пользователя из бана")] + [Alias("разбан")] + [RequireBotPermission(GuildPermission.BanMembers)] + [RequireUserPermission(GuildPermission.BanMembers)] + public Task Run(string user, [Remainder] string reason) { + var toBan = Utils.ParseUser(user).Result; + UnbanUser(Context.Guild, Context.User, toBan, reason); + return Task.CompletedTask; + } + + public static async void UnbanUser(IGuild guild, IUser author, IUser toUnban, string reason) { + var authorMention = author.Mention; + var notification = $"{authorMention} возвращает из бана {toUnban.Mention} за {Utils.WrapInline(reason)}"; + await guild.RemoveBanAsync(toUnban); + await Utils.SilentSendAsync(guild.GetSystemChannelAsync().Result, notification); + await Utils.SilentSendAsync(Utils.GetAdminLogChannel(), notification); + } +} \ No newline at end of file diff --git a/Boyfriend/Commands/UnmuteModule.cs b/Boyfriend/Commands/UnmuteModule.cs new file mode 100644 index 0000000..6135909 --- /dev/null +++ b/Boyfriend/Commands/UnmuteModule.cs @@ -0,0 +1,29 @@ +using Discord; +using Discord.Commands; +// ReSharper disable UnusedType.Global +// ReSharper disable UnusedMember.Global +// ReSharper disable ClassNeverInstantiated.Global + +namespace Boyfriend.Commands; + +public class UnmuteModule : ModuleBase { + + [Command("unmute")] + [Summary("Возвращает пользователя из мута")] + [Alias("размут")] + [RequireBotPermission(GuildPermission.ManageRoles)] + [RequireUserPermission(GuildPermission.ManageMessages)] + public Task Run(string user, [Remainder] string reason) { + var toUnmute = Utils.ParseMember(Context.Guild, user).Result; + UnmuteMember(Context.Guild, Context.User, toUnmute, reason); + return Task.CompletedTask; + } + + public static async void UnmuteMember(IGuild guild, IUser author, IGuildUser toUnmute, string reason) { + var authorMention = author.Mention; + var notification = $"{authorMention} возвращает из мута {toUnmute.Mention} за {Utils.WrapInline(reason)}"; + await toUnmute.RemoveRoleAsync(Utils.GetMuteRole(guild)); + await Utils.SilentSendAsync(guild.GetSystemChannelAsync().Result, notification); + await Utils.SilentSendAsync(Utils.GetAdminLogChannel(), notification); + } +} \ No newline at end of file diff --git a/Boyfriend/EventHandler.cs b/Boyfriend/EventHandler.cs index 95d970b..2c14e6d 100644 --- a/Boyfriend/EventHandler.cs +++ b/Boyfriend/EventHandler.cs @@ -19,23 +19,57 @@ public class EventHandler { await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), null); } + private static async Task HandleErrors(SocketCommandContext context, IResult result) { + var channel = context.Channel; + var reason = Utils.WrapInline(result.ErrorReason); + switch (result.Error) { + case CommandError.Exception: + await channel.SendMessageAsync($"Произошла непредвиденная ошибка при выполнении команды: {reason}"); + break; + case CommandError.Unsuccessful: + await channel.SendMessageAsync($"Выполнение команды завершилось неудачей: {reason}"); + break; + case CommandError.MultipleMatches: + await channel.SendMessageAsync($"Обнаружены повторяющиеся типы аргументов! {reason}"); + break; + case CommandError.ParseFailed: + await channel.SendMessageAsync($"Не удалось обработать команду: {reason}"); + break; + case CommandError.UnknownCommand: + await channel.SendMessageAsync($"Неизвестная команда! {reason}"); + break; + case CommandError.UnmetPrecondition: + await channel.SendMessageAsync($"У тебя недостаточно прав для выполнения этой команды! {reason}"); + break; + case CommandError.BadArgCount: + await channel.SendMessageAsync($"Неверное количество аргументов! {reason}"); + break; + case CommandError.ObjectNotFound: + await channel.SendMessageAsync($"Нету нужных аргументов! {reason}"); + break; + case null: + break; + default: + throw new ArgumentException("CommandError"); + + } + } + [Obsolete("Stop hard-coding things!")] private async Task ReadyEvent() { if (_client.GetChannel(618044439939645444) is not IMessageChannel botLogChannel) throw new ArgumentException("Invalid bot log channel"); - await botLogChannel.SendMessageAsync(Utils.GetBeep() + - "Я запустился! (C#)"); + await botLogChannel.SendMessageAsync($"{Utils.GetBeep()}Я запустился! (C#)"); } - private static async Task MessageDeletedEvent(Cacheable message, ISocketMessageChannel channel) { + private static async Task MessageDeletedEvent(Cacheable message, + Cacheable channel) { var msg = message.Value; - string toSend; - if (msg == null) - toSend = "Удалено сообщение в канале " + Utils.MentionChannel(channel.Id) + ", но я забыл что там было"; - else - toSend = "Удалено сообщение от " + msg.Author.Mention + " в канале " + Utils.MentionChannel(channel.Id) - + ": " + Utils.Wrap(msg.Content); - await Utils.GetAdminLogChannel().SendMessageAsync(toSend); + var toSend = msg == null + ? "Удалено сообщение в канале {Utils.MentionChannel(channel.Id)}, но я забыл что там было" + : $"Удалено сообщение от {msg.Author.Mention} в канале " + + $"{Utils.MentionChannel(channel.Id)}: {Environment.NewLine}{Utils.Wrap(msg.Content)}"; + await Utils.SilentSendAsync(Utils.GetAdminLogChannel(), toSend); } private async Task MessageReceivedEvent(SocketMessage messageParam) { @@ -45,7 +79,7 @@ public class EventHandler { if ((message.MentionedUsers.Count > 3 || message.MentionedRoles.Count > 2) && !user.GuildPermissions.MentionEveryone) - await new BanModule().BanUser(guild, guild.GetCurrentUserAsync().Result, user, TimeSpan.Zero, + BanModule.BanUser(guild, guild.GetCurrentUserAsync().Result, user, TimeSpan.FromMilliseconds(-1), "Более 3-ёх упоминаний в одном сообщении"); if (!(message.HasCharPrefix('!', ref argPos) || message.HasMentionPrefix(_client.CurrentUser, ref argPos)) || @@ -54,26 +88,26 @@ public class EventHandler { var context = new SocketCommandContext(_client, message); - await _commands.ExecuteAsync(context, argPos, null); + var result = await _commands.ExecuteAsync(context, argPos, null); + await HandleErrors(context, result); } private static async Task MessageUpdatedEvent(Cacheable messageCached, SocketMessage messageSocket, ISocketMessageChannel channel) { var msg = messageCached.Value; - string toSend; - if (msg == null) - toSend = "Отредактировано сообщение в канале " - + Utils.MentionChannel(channel.Id) + ", но я забыл что там было до редактирования: " - + Utils.Wrap(messageSocket.Content); - else - toSend = "Отредактировано сообщение от " + msg.Author.Mention + " в канале " - + Utils.MentionChannel(channel.Id) + ": " + Utils.Wrap(msg.Content) - + Utils.Wrap(messageSocket.Content); - await Utils.GetAdminLogChannel().SendMessageAsync(toSend); + var nl = Environment.NewLine; + var toSend = msg == null + ? $"Отредактировано сообщение от {messageSocket.Author.Mention} в канале" + + $" {Utils.MentionChannel(channel.Id)}," + $" но я забыл что там было до редактирования: " + + $"{Utils.Wrap(messageSocket.Content)}" + : $"Отредактировано сообщение от {msg.Author.Mention} " + + $"в канале {Utils.MentionChannel(channel.Id)}." + + $"{nl}До:{nl}{Utils.Wrap(msg.Content)}{nl}После:{nl}{Utils.Wrap(messageSocket.Content)}"; + await Utils.SilentSendAsync(Utils.GetAdminLogChannel(), toSend); } private static async Task UserJoinedEvent(SocketGuildUser user) { - await user.Guild.SystemChannel.SendMessageAsync(user.Mention + ", добро пожаловать на сервер " - + user.Guild.Name); + var guild = user.Guild; + await guild.SystemChannel.SendMessageAsync($"{user.Mention}, добро пожаловать на сервер {guild.Name}"); } } \ No newline at end of file diff --git a/Boyfriend/Utils.cs b/Boyfriend/Utils.cs index 29dec57..9c0ced5 100644 --- a/Boyfriend/Utils.cs +++ b/Boyfriend/Utils.cs @@ -1,25 +1,78 @@ -using Discord; +using System.Text.RegularExpressions; +using Discord; +using Discord.Net; namespace Boyfriend; public static class Utils { public static string GetBeep() { var letters = new[] { "а", "о", "и"}; - return "Б" + letters[new Random().Next(3)] + "п! "; + return $"Б{letters[new Random().Next(3)]}п! "; } [Obsolete("Stop hard-coding things!")] - public static IMessageChannel GetAdminLogChannel() { - if (Boyfriend.Client.GetChannel(870929165141032971) is not IMessageChannel adminLogChannel) + public static ITextChannel GetAdminLogChannel() { + if (Boyfriend.Client.GetChannel(870929165141032971) is not ITextChannel adminLogChannel) throw new ArgumentException("Invalid admin log channel"); return adminLogChannel; } public static string Wrap(string original) { - return original.Trim().Equals("") ? "" : "```" + original.Replace("```", "​`​`​`​") + "```"; + var toReturn = original.Replace("```", "​`​`​`​"); + return $"```{toReturn}{(toReturn.EndsWith("`") || toReturn.Trim().Equals("") ? " " : "")}```"; + } + + public static string WrapInline(string original) { + return $"`{original}`"; } public static string MentionChannel(ulong id) { - return "<#" + id + ">"; + return $"<#{id}>"; + } + + public static async Task StartDelayed(Task toRun, TimeSpan delay, Func? condition = null) { + switch (delay.TotalMilliseconds) { + case < -1: + throw new ArgumentOutOfRangeException(nameof(delay), "Указана отрицательная продолжительность!"); + case > int.MaxValue: + throw new ArgumentOutOfRangeException(nameof(delay), "Указана слишком большая продолжительность!"); + } + + await Task.Delay(delay); + var conditionResult = condition?.Invoke() ?? true; + if (conditionResult) + toRun.Start(); + } + + private static ulong ParseMention(string mention) { + return Convert.ToUInt64(Regex.Replace(mention, "[^0-9]", "")); + } + + public static async Task ParseUser(string mention) { + var user = Boyfriend.Client.GetUserAsync(ParseMention(mention)); + return await user; + } + + public static async Task ParseMember(IGuild guild, string mention) { + return await guild.GetUserAsync(ParseMention(mention)); + } + + public static async Task SendDirectMessage(IUser user, string toSend) { + try { + await user.SendMessageAsync(toSend); + } catch (HttpException e) { + if (e.DiscordCode != DiscordErrorCode.CannotSendMessageToUser) + throw; + } + } + + public static IRole GetMuteRole(IGuild guild) { + var role = guild.Roles.FirstOrDefault(x => x.Name.ToLower() is "заключённый" or "muted"); + if (role == null) throw new Exception("Не удалось найти роль мута"); + return role; + } + + public static async Task SilentSendAsync(ITextChannel channel, string text) { + await channel.SendMessageAsync(text, false, null, null, AllowedMentions.None); } } \ No newline at end of file