diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 643492d..93a190c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ -* @LabsDevelopment/octobot -/docs/ @LabsDevelopment/octobot-docs +* @TeamOctolings/octobot +/docs/ @TeamOctolings/octobot-docs diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index b697dac..e93a460 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@v4 - name: ReSharper CLI InspectCode - uses: muno92/resharper_inspectcode@1.11.1 + uses: muno92/resharper_inspectcode@1.11.5 with: solutionPath: ./Octobot.sln ignoreIssueType: InvertIf, ConvertIfStatementToSwitchStatement, ConvertToPrimaryConstructor diff --git a/Octobot.csproj b/Octobot.csproj index e8f0dfa..1f050a6 100644 --- a/Octobot.csproj +++ b/Octobot.csproj @@ -9,11 +9,11 @@ Octobot Octol1ttle, mctaylors, neroduckale AGPLv3 - https://github.com/LabsDevelopment/Octobot - https://github.com/LabsDevelopment/Octobot/blob/master/LICENSE - https://github.com/LabsDevelopment/Octobot + https://github.com/TeamOctolings/Octobot + https://github.com/TeamOctolings/Octobot/blob/master/LICENSE + https://github.com/TeamOctolings/Octobot github - LabsDevelopment + TeamOctolings en A general-purpose Discord bot for moderation written in C# docs/octobot.ico diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 2a15ef2..dc5a793 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -29,7 +29,7 @@ While pull requests from unaffiliated contributors are welcome, please note that internal issues that haven't been published to the issue tracker yet. Reviewing PRs is done on a best-effort basis, so please be aware that it may take a while before a core maintainer gets around to review your change. -The [issue tracker](https://github.com/LabsDevelopment/Octobot/issues) should provide plenty of issues to start with. +The [issue tracker](https://github.com/TeamOctolings/Octobot/issues) should provide plenty of issues to start with. Make sure to check that an issue you're planning to resolve does not already have people working on it and that there are no PRs associated with it diff --git a/docs/README.md b/docs/README.md index 5be0bd8..7056857 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,12 +1,12 @@

- Octobot banner + Octobot banner

- + - + -Veemo! I'm a general-purpose bot for moderation (formerly known as Boyfriend) written by [Labs Development Team](https://github.com/LabsDevelopment) in C# and Remora.Discord +Veemo! I'm a general-purpose bot for moderation (formerly known as Boyfriend) written by [Team Octolings](https://github.com/TeamOctolings) in C# and Remora.Discord ## Features @@ -19,25 +19,13 @@ Veemo! I'm a general-purpose bot for moderation (formerly known as Boyfriend) wr *...a-a-and more!* -[//]: # (if you are reading this, message @mctaylors and ask him to bring back the wiki) - -## Invite Octobot - -Did you know that Octobot is a public bot? You can invite it to your server and use it without building it! -

- -

- -> [!IMPORTANT] -> The bot will not be able to respond in private channels unless you have configured permissions for the bot in those channels. - ## Building Octobot 1. Install [.NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) 2. Go to the [Discord Developer Portal](https://discord.com/developers), create a new application and get a bot token. Don't forget to also enable all intents! 3. Clone this repository and open `Octobot` folder. ``` -git clone https://github.com/LabsDevelopment/Octobot +git clone https://github.com/TeamOctolings/Octobot cd Octobot ``` 4. Run Octobot using `dotnet` with `BOT_TOKEN` variable. diff --git a/docs/octobot-banner.png b/docs/octobot-banner.png new file mode 100644 index 0000000..2ab5f5b Binary files /dev/null and b/docs/octobot-banner.png differ diff --git a/locale/Messages.resx b/locale/Messages.resx index adc9f6d..218c414 100644 --- a/locale/Messages.resx +++ b/locale/Messages.resx @@ -591,4 +591,7 @@ Kicked + + Reminder edited + diff --git a/locale/Messages.ru.resx b/locale/Messages.ru.resx index de2158d..3eb53f1 100644 --- a/locale/Messages.ru.resx +++ b/locale/Messages.ru.resx @@ -591,4 +591,7 @@ Выгнан + + Напоминание отредактировано + diff --git a/locale/Messages.tt-ru.resx b/locale/Messages.tt-ru.resx index ca3c19d..f5d789b 100644 --- a/locale/Messages.tt-ru.resx +++ b/locale/Messages.tt-ru.resx @@ -591,4 +591,7 @@ кикнут + + напоминалка подправлена + diff --git a/src/Commands/AboutCommandGroup.cs b/src/Commands/AboutCommandGroup.cs index 9fc1f04..3ff2c66 100644 --- a/src/Commands/AboutCommandGroup.cs +++ b/src/Commands/AboutCommandGroup.cs @@ -115,7 +115,7 @@ public class AboutCommandGroup : CommandGroup .WithSmallTitle(string.Format(Messages.AboutBot, bot.Username), bot) .WithDescription(builder.ToString()) .WithColour(ColorsList.Cyan) - .WithImageUrl("https://cdn.mctaylors.ru/octobot-banner.png") + .WithImageUrl("https://i.ibb.co/fS6wZhh/octobot-banner.png") .Build(); var repositoryButton = new ButtonComponent( diff --git a/src/Commands/BanCommandGroup.cs b/src/Commands/BanCommandGroup.cs index 2abb5c8..06d5404 100644 --- a/src/Commands/BanCommandGroup.cs +++ b/src/Commands/BanCommandGroup.cs @@ -57,7 +57,7 @@ public class BanCommandGroup : CommandGroup /// A slash command that bans a Discord user with the specified reason. /// /// The user to ban. - /// The duration for this ban. The user will be automatically unbanned after this duration. + /// The duration for this ban. The user will be automatically unbanned after this duration. /// /// The reason for this ban. Must be encoded with when passed to /// . @@ -79,8 +79,7 @@ public class BanCommandGroup : CommandGroup [Description("User to ban")] IUser target, [Description("Ban reason")] [MaxLength(256)] string reason, - [Description("Ban duration")] [Option("duration")] - string? stringDuration = null) + [Description("Ban duration")] string? duration = null) { _profiler.Push("ban_command"); _profiler.Push("preparation"); @@ -120,7 +119,7 @@ public class BanCommandGroup : CommandGroup Messages.Culture = GuildSettings.Language.Get(data.Settings); _profiler.Pop(); - if (stringDuration is null) + if (duration is null) { _profiler.Pop(); return _profiler.ReportWithResult(await BanUserAsync(executor, target, reason, null, guild, data, channelId, @@ -128,8 +127,8 @@ public class BanCommandGroup : CommandGroup CancellationToken)); } - var parseResult = TimeSpanParser.TryParse(stringDuration); - if (!parseResult.IsDefined(out var duration)) + var parseResult = TimeSpanParser.TryParse(duration); + if (!parseResult.IsDefined(out var timeSpan)) { _profiler.Push("invalid_timespan_send"); var failedEmbed = new EmbedBuilder() @@ -142,7 +141,7 @@ public class BanCommandGroup : CommandGroup } _profiler.Pop(); - return _profiler.ReportWithResult(await BanUserAsync(executor, target, reason, duration, guild, data, channelId, + return _profiler.ReportWithResult(await BanUserAsync(executor, target, reason, timeSpan, guild, data, channelId, bot, CancellationToken)); } diff --git a/src/Commands/RemindCommandGroup.cs b/src/Commands/RemindCommandGroup.cs index 584625e..09887e5 100644 --- a/src/Commands/RemindCommandGroup.cs +++ b/src/Commands/RemindCommandGroup.cs @@ -236,6 +236,132 @@ public class RemindCommandGroup : CommandGroup return _profiler.PopWithResult(await _feedback.SendContextualEmbedResultAsync(embed, ct: ct)); } + public enum Parameters + { + [UsedImplicitly] Time, + [UsedImplicitly] Text + } + + /// + /// A slash command that edits a scheduled reminder using the specified text or time. + /// + /// The list position of the reminder to edit. + /// The reminder's parameter to edit. + /// The new value for the reminder as a text or time. + /// A feedback sending result which may or may not have succeeded. + [Command("editremind")] + [Description("Edit a reminder")] + [DiscordDefaultDMPermission(false)] + [RequireContext(ChannelContext.Guild)] + [UsedImplicitly] + public async Task ExecuteEditReminderAsync( + [Description("Position in list")] [MinValue(1)] + int position, + [Description("Parameter to edit")] Parameters parameter, + [Description("Parameter's new value")] string value) + { + if (!_context.TryGetContextIDs(out var guildId, out _, out var executorId)) + { + return new ArgumentInvalidError(nameof(_context), "Unable to retrieve necessary IDs from command context"); + } + + var botResult = await _userApi.GetCurrentUserAsync(CancellationToken); + if (!botResult.IsDefined(out var bot)) + { + return Result.FromError(botResult); + } + + var executorResult = await _userApi.GetUserAsync(executorId, CancellationToken); + if (!executorResult.IsDefined(out var executor)) + { + return Result.FromError(executorResult); + } + + var data = await _guildData.GetData(guildId, CancellationToken); + Messages.Culture = GuildSettings.Language.Get(data.Settings); + + var memberData = data.GetOrCreateMemberData(executor.ID); + + if (parameter is Parameters.Time) + { + return await EditReminderTimeAsync(position - 1, value, memberData, bot, executor, CancellationToken); + } + + return await EditReminderTextAsync(position - 1, value, memberData, bot, executor, CancellationToken); + } + + private async Task EditReminderTimeAsync(int index, string value, MemberData data, + IUser bot, IUser executor, CancellationToken ct = default) + { + if (index >= data.Reminders.Count) + { + var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.InvalidReminderPosition, bot) + .WithColour(ColorsList.Red) + .Build(); + + return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); + } + + var parseResult = TimeSpanParser.TryParse(value); + if (!parseResult.IsDefined(out var timeSpan)) + { + var failedEmbed = new EmbedBuilder() + .WithSmallTitle(Messages.InvalidTimeSpan, bot) + .WithColour(ColorsList.Red) + .Build(); + + return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); + } + + var oldReminder = data.Reminders[index]; + var remindAt = DateTimeOffset.UtcNow.Add(timeSpan); + + data.Reminders.Add(oldReminder with { At = remindAt }); + data.Reminders.RemoveAt(index); + + var builder = new StringBuilder() + .AppendBulletPointLine(string.Format(Messages.ReminderText, Markdown.InlineCode(oldReminder.Text))) + .AppendBulletPoint(string.Format(Messages.ReminderTime, Markdown.Timestamp(remindAt))); + var embed = new EmbedBuilder().WithSmallTitle( + string.Format(Messages.ReminderEdited, executor.GetTag()), executor) + .WithDescription(builder.ToString()) + .WithColour(ColorsList.Cyan) + .WithFooter(string.Format(Messages.ReminderPosition, data.Reminders.Count)) + .Build(); + + return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); + } + + private async Task EditReminderTextAsync(int index, string value, MemberData data, + IUser bot, IUser executor, CancellationToken ct = default) + { + if (index >= data.Reminders.Count) + { + var failedEmbed = new EmbedBuilder().WithSmallTitle(Messages.InvalidReminderPosition, bot) + .WithColour(ColorsList.Red) + .Build(); + + return await _feedback.SendContextualEmbedResultAsync(failedEmbed, ct: ct); + } + + var oldReminder = data.Reminders[index]; + + data.Reminders.Add(oldReminder with { Text = value }); + data.Reminders.RemoveAt(index); + + var builder = new StringBuilder() + .AppendBulletPointLine(string.Format(Messages.ReminderText, Markdown.InlineCode(value))) + .AppendBulletPoint(string.Format(Messages.ReminderTime, Markdown.Timestamp(oldReminder.At))); + var embed = new EmbedBuilder().WithSmallTitle( + string.Format(Messages.ReminderEdited, executor.GetTag()), executor) + .WithDescription(builder.ToString()) + .WithColour(ColorsList.Cyan) + .WithFooter(string.Format(Messages.ReminderPosition, data.Reminders.Count)) + .Build(); + + return await _feedback.SendContextualEmbedResultAsync(embed, ct: ct); + } + /// /// A slash command that deletes a reminder using its list position. /// diff --git a/src/Messages.Designer.cs b/src/Messages.Designer.cs index f5a06c0..a0c915a 100644 --- a/src/Messages.Designer.cs +++ b/src/Messages.Designer.cs @@ -1052,5 +1052,11 @@ namespace Octobot { return ResourceManager.GetString("UserInfoKicked", resourceCulture); } } + + internal static string ReminderEdited { + get { + return ResourceManager.GetString("ReminderEdited", resourceCulture); + } + } } } diff --git a/src/Octobot.cs b/src/Octobot.cs index 3049602..d1894e7 100644 --- a/src/Octobot.cs +++ b/src/Octobot.cs @@ -28,7 +28,7 @@ namespace Octobot; public sealed class Octobot { - public const string RepositoryUrl = "https://github.com/LabsDevelopment/Octobot"; + public const string RepositoryUrl = "https://github.com/TeamOctolings/Octobot"; public const string IssuesUrl = $"{RepositoryUrl}/issues"; public static readonly AllowedMentions NoMentions = new( diff --git a/src/Responders/MessageReceivedResponder.cs b/src/Responders/MessageReceivedResponder.cs index f5c65f4..6ab7199 100644 --- a/src/Responders/MessageReceivedResponder.cs +++ b/src/Responders/MessageReceivedResponder.cs @@ -28,9 +28,10 @@ public class MessageCreateResponder : IResponder "whoami" => "`nobody`", "сука !!" => "`root`", "воооо" => "`removing /...`", - "пон" => "https://cdn.upload.systems/uploads/2LNfUSwM.jpg", + "пон" => "https://i.ibb.co/Kw6QVcw/parry.jpg", "++++" => "#", "осу" => "https://github.com/ppy/osu", + "лан" => "https://i.ibb.co/VYH2QLc/lan.jpg", _ => default(Optional) }); return Task.FromResult(Result.FromSuccess());