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