From a5e912ff0d66f11e1f2bf0969ef29eda672bdc94 Mon Sep 17 00:00:00 2001 From: Abner <25711694+AbnerSquared@users.noreply.github.com> Date: Fri, 13 Mar 2020 05:02:36 -0500 Subject: [PATCH] Create Help Command and Refactor (#275) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement help command basics * Update all dependencies and StyleCop pass This ensures that all projects within Miunie are now set to the latest .NET Core, .NET Standard, and C# Language versions. All dependencies specified in Miunie were updated to their most recent counterparts. All classes mentioned were modified to follow StyleCop standards. One readonly value was made in EmbedConstructor to simplify .WithColor() when creating new embeds. All existing summaries were moved into HelpStrings.resx. HelpService now attempts to fetch a summary for the specified ID in SummaryAttribute for a CommandInfo, and will now use ILanguageProvider for default string values. * Set LangVersion to 8.0 and refactor This update should fix all issues previously mentioned and make some methods easier to handle. Changes - Created StringExtensions, which contains two methods of ValueOrDefault(this string, string) and JoinOrDefault(this IEnumerable, string, string). - Created TExtensions, which contains one method of StringJoinOrDefault(this IEnumerable, Func, string, string). - Set all LangVersion properties to 8.0. - Modified HelpCommand, MiscCommands, ProfileCommand, RemoteRepositoryCommand, and TimeCommand to store their direct summaries and include examples. - Modified HelpCommandProvider to include three new methods of GetAllModuleSections(), GetSection(ModuleInfo), and GetSection(CommandInfo). - Modified HelpCommandProvider.Search to use GetSection(Command). - Modified HelpCommandProvider.GetDefault to use GetAllModuleSections(). - Modified HelpCommandProvider.GetExamples(CommandInfo) to use .StringJoinOrDefault(). - Modified HelpResult.Sections to implement IEnumerable instead. - Modified HelpResult.Sections to expose the set property. - Modified Strings.resx to make HELP_SUMMARY_EMPTY and HELP_EXAMPLE_EMPTY include multiple response values. - Modified EmbedConstructor.GetHelpEmbed(HelpResult) to use MiuniePinkColor for its EmbedBuilder. - Renamed EmbedConstructor.DefaultEmbedColor to MiuniePinkColor and removed comment. - Renamed HelpService to HelpCommandProvider. - Renamed CommandService.GetHelpService(ILanguageProvider) to CommandService.GetHelpProvider(ILanguageProvider). - Reverted all .NET Standard targets back to 2.0. - Deleted HelpStrings.resx. * Replace collection count property A quick change that fixes the previous string[].Length to now use the required IEnumerable.Count(). * Apply implicit declaration where needed Another minor fix that applies implicit declaration where it was needed, alongside adding a blank line in between a variable and a for each statement. * Minor refactor in code - Created new method GetModuleCommandBlocks(ModuleInfo) in HelpCommandProvider. - Modified ProfileCommand to use "to" instead of "for" in both summaries. - Modified HelpCommandProvider.GetDefault() to use _commandService.Modules.Select() instead. - Modified HelpCommandProvider.GetSection(ModuleInfo) to use GetModuleCommandBlocks(). - Removed HelpCommandProvider.GetAllModuleHelpSections(). * Change search method format - Modified HelpCommandProvider.Search(string) to use the similar .Select() method used in GetDefault(). * Adjust methods and dependencies - Modify Container.AddMiunieTypes(this IServiceCollection) to include CommandService - Modify method GetHelp() in HelpCommand to handle sending the message result - Modify method GetHelp(string) in HelpCommand to handle sending the message result - Renamed HelpCommandProvider to CommandHelpProvider - Rename method GetDefault() to ForAllCommands() in CommandHelpProvider - Rename method Search(string) to FromInput(string) in CommandHelpProvider - Remove method GetHelpProvider(ILanguageProvider) from CommandHandler - Remove method ShowDefaultHelpAsync(IMessageChannel) in CommandHelpProvider - Remove method ShowCommandHelpAsync(IMessageChannel, string) from CommandHelpProvider - Delete StringExtensions - Delete TExtensions * Rename private variables This quick formatting fix properly renames the specified variables, as brought to light by 1n5an1ty. * Properly rename private variables This push now correctly renames the private variables mentioned. * Update src/Miunie.Discord/Providers/CommandHelpProvider.cs Co-authored-by: Petr Sedláček --- src/Miunie.Avalonia/Miunie.Avalonia.csproj | 8 +- .../Miunie.ConsoleApp.csproj | 4 +- .../Miunie.Core.XUnit.Tests.csproj | 13 +- src/Miunie.Core/Entities/PhraseKey.cs | 5 + src/Miunie.Core/Strings.Designer.cs | 51 +++++++ src/Miunie.Core/Strings.resx | 26 ++++ .../Adapters/DiscordMessagesAdapter.cs | 1 - .../Attributes/ExamplesAttribute.cs | 30 ++++ src/Miunie.Discord/CommandHandler.cs | 4 +- .../CommandModules/HelpCommand.cs | 52 +++++++ .../CommandModules/MiscCommands.cs | 4 + .../CommandModules/ProfileCommand.cs | 14 ++ .../CommandModules/RemoteRepositoryCommand.cs | 5 +- .../CommandModules/TimeCommand.cs | 14 ++ src/Miunie.Discord/Embeds/EmbedConstructor.cs | 44 ++++-- src/Miunie.Discord/Entities/HelpResult.cs | 26 ++++ src/Miunie.Discord/Entities/HelpSection.cs | 30 ++++ .../Extensions/CommandInfoExtensions.cs | 33 +++++ src/Miunie.Discord/Miunie.Discord.csproj | 1 + .../Providers/CommandHelpProvider.cs | 129 ++++++++++++++++++ .../TypeReaders/MiunieUserTypeReader.cs | 2 +- src/Miunie.InversionOfControl/Container.cs | 2 + .../Miunie.InversionOfControl.csproj | 1 + .../Miunie.LiteDbStorage.csproj | 5 +- src/Miunie.Logger/Miunie.Logger.csproj | 1 + .../Miunie.SystemInfrastructure.csproj | 1 + 26 files changed, 473 insertions(+), 33 deletions(-) create mode 100644 src/Miunie.Discord/Attributes/ExamplesAttribute.cs create mode 100644 src/Miunie.Discord/CommandModules/HelpCommand.cs create mode 100644 src/Miunie.Discord/Entities/HelpResult.cs create mode 100644 src/Miunie.Discord/Entities/HelpSection.cs create mode 100644 src/Miunie.Discord/Extensions/CommandInfoExtensions.cs create mode 100644 src/Miunie.Discord/Providers/CommandHelpProvider.cs diff --git a/src/Miunie.Avalonia/Miunie.Avalonia.csproj b/src/Miunie.Avalonia/Miunie.Avalonia.csproj index 3903f86..9519671 100644 --- a/src/Miunie.Avalonia/Miunie.Avalonia.csproj +++ b/src/Miunie.Avalonia/Miunie.Avalonia.csproj @@ -1,7 +1,7 @@  WinExe - netcoreapp3.0 + netcoreapp3.1 8.0 ../.ruleset @@ -16,9 +16,9 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Miunie.ConsoleApp/Miunie.ConsoleApp.csproj b/src/Miunie.ConsoleApp/Miunie.ConsoleApp.csproj index 6b3aaec..f335da0 100644 --- a/src/Miunie.ConsoleApp/Miunie.ConsoleApp.csproj +++ b/src/Miunie.ConsoleApp/Miunie.ConsoleApp.csproj @@ -3,13 +3,13 @@ Exe netcoreapp3.1 - latest + 8.0 ../.ruleset - + diff --git a/src/Miunie.Core.XUnit.Tests/Miunie.Core.XUnit.Tests.csproj b/src/Miunie.Core.XUnit.Tests/Miunie.Core.XUnit.Tests.csproj index 542fe0c..47d46b6 100644 --- a/src/Miunie.Core.XUnit.Tests/Miunie.Core.XUnit.Tests.csproj +++ b/src/Miunie.Core.XUnit.Tests/Miunie.Core.XUnit.Tests.csproj @@ -2,17 +2,20 @@ netcoreapp3.1 - false ../.ruleset + 8.0 - + - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Miunie.Core/Entities/PhraseKey.cs b/src/Miunie.Core/Entities/PhraseKey.cs index d66d469..1e6b6cd 100644 --- a/src/Miunie.Core/Entities/PhraseKey.cs +++ b/src/Miunie.Core/Entities/PhraseKey.cs @@ -23,6 +23,7 @@ public enum PhraseKey SHOW_REMOTE_REPO, USER_EMBED_TITLE, USER_EMBED_NAME_TITLE, + USER_EMBED_HELP_TITLE, USER_EMBED_IS_BOT, USER_EMBED_IS_HUMAN, USER_EMBED_REALNESS_TITLE, @@ -54,6 +55,10 @@ public enum PhraseKey INCORRECT_VERB, REPUTATION_TAKEN_BOT, REPUTATION_GIVEN_BOT, + HELP_EXAMPLE_EMPTY, + HELP_EXAMPLE_TITLE, + HELP_SUMMARY_EMPTY, + HELP_SUMMARY_TITLE, GPL3_NOTICE } } diff --git a/src/Miunie.Core/Strings.Designer.cs b/src/Miunie.Core/Strings.Designer.cs index 1c1fa71..d665e12 100644 --- a/src/Miunie.Core/Strings.Designer.cs +++ b/src/Miunie.Core/Strings.Designer.cs @@ -131,6 +131,48 @@ public static string GUILD_EMBED_TITLE { } } + /// + /// Looks up a localized string similar to You'll figure it out.{{OR}} + ///You know what? I've never used this command.{{OR}} + ///Yeah, you're on your own with this one, buddy.{{OR}} + ///I got nothing here.{{OR}} + ///Just mess around with this, you'll figure it out eventually.. + /// + public static string HELP_EXAMPLE_EMPTY { + get { + return ResourceManager.GetString("HELP_EXAMPLE_EMPTY", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to **Examples**: . + /// + public static string HELP_EXAMPLE_TITLE { + get { + return ResourceManager.GetString("HELP_EXAMPLE_TITLE", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This is kind of difficult to explain...{{OR}} + ///How am I supposed to put this into words?{{OR}} + ///It's a command, I think? Not sure.. + /// + public static string HELP_SUMMARY_EMPTY { + get { + return ResourceManager.GetString("HELP_SUMMARY_EMPTY", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to **Summary**: . + /// + public static string HELP_SUMMARY_TITLE { + get { + return ResourceManager.GetString("HELP_SUMMARY_TITLE", resourceCulture); + } + } + /// /// Looks up a localized string similar to You better start making sense with other than '{0}'{{OR}} ///Come again, I don't understand '{0}'{{OR}} @@ -318,6 +360,15 @@ public static string USER_EMBED_CREATED_AT_TITLE { } } + /// + /// Looks up a localized string similar to **HELP MENU**. + /// + public static string USER_EMBED_HELP_TITLE { + get { + return ResourceManager.GetString("USER_EMBED_HELP_TITLE", resourceCulture); + } + } + /// /// Looks up a localized string similar to _A software person._{{OR}} ///_A bot._ :robot:{{OR}} diff --git a/src/Miunie.Core/Strings.resx b/src/Miunie.Core/Strings.resx index b8e7f28..7d0789a 100644 --- a/src/Miunie.Core/Strings.resx +++ b/src/Miunie.Core/Strings.resx @@ -158,6 +158,28 @@ Source code available at: {1} **ABOUT THIS SERVER** No formatting options. + + You'll figure it out.{{OR}} +You know what? I've never used this command.{{OR}} +Yeah, you're on your own with this one, buddy.{{OR}} +I got nothing here.{{OR}} +Just mess around with this, you'll figure it out eventually. + No formatting options. + + + **Examples**: + No formatting options. + + + This is kind of difficult to explain...{{OR}} +How am I supposed to put this into words?{{OR}} +It's a command, I think? Not sure. + No formatting options. + + + **Summary**: + No formatting options. + You better start making sense with other than '{0}'{{OR}} Come again, I don't understand '{0}'{{OR}} @@ -258,6 +280,10 @@ What are you trying to say with {0} {1} :calendar_spiral: Creation date No formatting options. + + **HELP MENU** + No formatting options. + _A software person._{{OR}} _A bot._ :robot:{{OR}} diff --git a/src/Miunie.Discord/Adapters/DiscordMessagesAdapter.cs b/src/Miunie.Discord/Adapters/DiscordMessagesAdapter.cs index 08275a3..d1c906c 100644 --- a/src/Miunie.Discord/Adapters/DiscordMessagesAdapter.cs +++ b/src/Miunie.Discord/Adapters/DiscordMessagesAdapter.cs @@ -14,7 +14,6 @@ // along with Miunie. If not, see . using Discord.WebSocket; -using Miunie.Core; using Miunie.Core.Discord; using Miunie.Core.Entities; using Miunie.Core.Entities.Discord; diff --git a/src/Miunie.Discord/Attributes/ExamplesAttribute.cs b/src/Miunie.Discord/Attributes/ExamplesAttribute.cs new file mode 100644 index 0000000..e03df55 --- /dev/null +++ b/src/Miunie.Discord/Attributes/ExamplesAttribute.cs @@ -0,0 +1,30 @@ +// This file is part of Miunie. +// +// Miunie is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Miunie is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Miunie. If not, see . + +using System; + +namespace Miunie.Discord.Attributes +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class ExamplesAttribute : Attribute + { + public ExamplesAttribute(params string[] examples) + { + Examples = examples; + } + + public string[] Examples { get; } + } +} diff --git a/src/Miunie.Discord/CommandHandler.cs b/src/Miunie.Discord/CommandHandler.cs index e6b06d2..f42b611 100644 --- a/src/Miunie.Discord/CommandHandler.cs +++ b/src/Miunie.Discord/CommandHandler.cs @@ -35,10 +35,10 @@ public class CommandHandler private readonly EntityConvertor _convertor; private readonly IBotConfiguration _botConfig; - public CommandHandler(IDiscord discord, IServiceProvider services, ILogWriter logger, EntityConvertor convertor, IBotConfiguration botConfig) + public CommandHandler(IDiscord discord, CommandService commandService, IServiceProvider services, ILogWriter logger, EntityConvertor convertor, IBotConfiguration botConfig) { _discord = discord; - _commandService = new CommandService(); + _commandService = commandService; _services = services; _logger = logger; _convertor = convertor; diff --git a/src/Miunie.Discord/CommandModules/HelpCommand.cs b/src/Miunie.Discord/CommandModules/HelpCommand.cs new file mode 100644 index 0000000..135e70d --- /dev/null +++ b/src/Miunie.Discord/CommandModules/HelpCommand.cs @@ -0,0 +1,52 @@ +// This file is part of Miunie. +// +// Miunie is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Miunie is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Miunie. If not, see . + +using Discord.Commands; +using Miunie.Core.Providers; +using Miunie.Discord.Attributes; +using Miunie.Discord.Embeds; +using System.Threading.Tasks; + +namespace Miunie.Discord.CommandModules +{ + [Name("Help")] + public class HelpCommand : ModuleBase + { + private readonly CommandHelpProvider _helpProvider; + + public HelpCommand(CommandService commandService, ILanguageProvider lang) + { + _helpProvider = new CommandHelpProvider(commandService, lang); + } + + [Command("help")] + [Summary("I'm here for you.")] + [Examples("help")] + public async Task GetHelp() + { + var helpResult = _helpProvider.ForAllCommands(); + _ = await Context.Channel.SendMessageAsync(embed: EmbedConstructor.CreateHelpEmbed(helpResult)); + } + + [Command("help")] + [Summary("Gets help for a specified command.")] + [Examples("help repo")] + public async Task GetHelp([Remainder]string input) + { + var helpResult = _helpProvider.FromInput(input); + _ = await Context.Channel.SendMessageAsync(embed: EmbedConstructor.CreateHelpEmbed(helpResult)); + } + } +} diff --git a/src/Miunie.Discord/CommandModules/MiscCommands.cs b/src/Miunie.Discord/CommandModules/MiscCommands.cs index 18d1afa..d2630de 100644 --- a/src/Miunie.Discord/CommandModules/MiscCommands.cs +++ b/src/Miunie.Discord/CommandModules/MiscCommands.cs @@ -16,11 +16,13 @@ using Discord.Commands; using Discord.WebSocket; using Miunie.Core; +using Miunie.Discord.Attributes; using Miunie.Discord.Convertors; using System.Threading.Tasks; namespace Miunie.Discord.CommandModules { + [Name("Misc")] public class MiscCommands : ModuleBase { private readonly MiscService _service; @@ -33,6 +35,8 @@ public MiscCommands(MiscService service, EntityConvertor entityConvertor) } [Command("what do you think?")] + [Summary("What do I think? I guess you'll have to find out~")] + [Examples("what do you think?")] public async Task SendRandomYesNoMaybeAnswer() { var c = _entityConvertor.ConvertChannel(Context.Channel as SocketGuildChannel); diff --git a/src/Miunie.Discord/CommandModules/ProfileCommand.cs b/src/Miunie.Discord/CommandModules/ProfileCommand.cs index 12584e6..a33cc1c 100644 --- a/src/Miunie.Discord/CommandModules/ProfileCommand.cs +++ b/src/Miunie.Discord/CommandModules/ProfileCommand.cs @@ -17,11 +17,13 @@ using Discord.WebSocket; using Miunie.Core; using Miunie.Core.Entities.Discord; +using Miunie.Discord.Attributes; using Miunie.Discord.Convertors; using System.Threading.Tasks; namespace Miunie.Discord.CommandModules { + [Name("Profile")] public class ProfileCommand : ModuleBase { private readonly EntityConvertor _entityConvertor; @@ -34,6 +36,8 @@ public ProfileCommand(EntityConvertor entityConvertor, ProfileService profileSer } [Command("profile")] + [Summary("Pull up a dank profile!")] + [Examples("profile", "profile @Miunie")] public async Task ShowProfileAsync(MiunieUser user = null) { if (user is null) @@ -46,6 +50,8 @@ public async Task ShowProfileAsync(MiunieUser user = null) } [Command("rep log")] + [Summary("Pull up your current reputation log~")] + [Examples("rep log", "rep log 1")] public async Task ShowReputationLogAsync(int page = 1) { var source = _entityConvertor.ConvertUser(Context.User as SocketGuildUser); @@ -54,6 +60,8 @@ public async Task ShowReputationLogAsync(int page = 1) } [Command("rep log for")] + [Summary("Pull up a reputation log for the specified user!")] + [Examples("rep log for @Miunie", "rep log for @Mackie 1")] public async Task ShowReputationLogAsync(MiunieUser user, int page = 1) { var source = _entityConvertor.ConvertUser(Context.User as SocketGuildUser); @@ -62,6 +70,8 @@ public async Task ShowReputationLogAsync(MiunieUser user, int page = 1) } [Command("+rep")] + [Summary("Give a good ol' positive reputation to a user!")] + [Examples("+rep @Miunie")] public async Task AddReputationAsync(MiunieUser user) { var source = _entityConvertor.ConvertUser(Context.User as SocketGuildUser); @@ -70,6 +80,8 @@ public async Task AddReputationAsync(MiunieUser user) } [Command("-rep")] + [Summary("Give a cold-blooded negative reputation to a user...")] + [Examples("-rep @You")] public async Task RemoveReputationAsync(MiunieUser user) { var source = _entityConvertor.ConvertUser(Context.User as SocketGuildUser); @@ -78,6 +90,8 @@ public async Task RemoveReputationAsync(MiunieUser user) } [Command("guild")] + [Summary("Pull up information about this guild.")] + [Examples("guild")] public async Task ShowGuildInfoAsync() { var guild = _entityConvertor.ConvertGuild(Context.Guild); diff --git a/src/Miunie.Discord/CommandModules/RemoteRepositoryCommand.cs b/src/Miunie.Discord/CommandModules/RemoteRepositoryCommand.cs index 113a72a..c46527e 100644 --- a/src/Miunie.Discord/CommandModules/RemoteRepositoryCommand.cs +++ b/src/Miunie.Discord/CommandModules/RemoteRepositoryCommand.cs @@ -16,11 +16,13 @@ using Discord.Commands; using Discord.WebSocket; using Miunie.Core; +using Miunie.Discord.Attributes; using Miunie.Discord.Convertors; using System.Threading.Tasks; namespace Miunie.Discord.CommandModules { + [Name("Remote Repository")] public class RemoteRepositoryCommand : ModuleBase { private readonly RemoteRepositoryService _remoteRepoService; @@ -33,7 +35,8 @@ public RemoteRepositoryCommand(RemoteRepositoryService remoteRepoService, Entity } [Command("repo")] - [Summary("Shows the official remote repository hosting the code of this bot")] + [Summary("Shows the official remote repository hosting the code of this bot.")] + [Examples("repo")] public async Task ShowRepository() { var channel = _entityConvertor.ConvertChannel(Context.Channel as SocketGuildChannel); diff --git a/src/Miunie.Discord/CommandModules/TimeCommand.cs b/src/Miunie.Discord/CommandModules/TimeCommand.cs index 94d615a..f920706 100644 --- a/src/Miunie.Discord/CommandModules/TimeCommand.cs +++ b/src/Miunie.Discord/CommandModules/TimeCommand.cs @@ -18,12 +18,14 @@ using Discord.WebSocket; using Miunie.Core; using Miunie.Core.Entities.Discord; +using Miunie.Discord.Attributes; using Miunie.Discord.Convertors; using System; using System.Threading.Tasks; namespace Miunie.Discord.CommandModules { + [Name("Time")] public class TimeCommand : ModuleBase { private readonly TimeService _service; @@ -36,6 +38,8 @@ public TimeCommand(TimeService service, EntityConvertor entityConvertor) } [Command("time for")] + [Summary("Gets the local time for the specified user.")] + [Examples("time for @Miunie")] public async Task ShowTimeForUser(MiunieUser user) { var c = _entityConvertor.ConvertChannel(Context.Channel as SocketGuildChannel); @@ -43,6 +47,8 @@ public async Task ShowTimeForUser(MiunieUser user) } [Command("time for")] + [Summary("Gets the local time offset by a specified amount for a user.")] + [Examples("time for @Miunie in 4 hours", "time for @Peter in 2 minutes", "time for @Draxis in 1 second")] public async Task ShowTimeForUserWithOffset(MiunieUser user, string verb, int units, string timeframe) { var c = _entityConvertor.ConvertChannel(Context.Channel as SocketGuildChannel); @@ -50,6 +56,8 @@ public async Task ShowTimeForUserWithOffset(MiunieUser user, string verb, int un } [Command("time get")] + [Summary("Compares a user's time with your time.")] + [Examples("time get 03:30 for @Miunie")] public async Task ShowTimeForUserComparedToCurrentUser(DateTime requestTime, string verb, MiunieUser user) { var u = _entityConvertor.ConvertUser(Context.User as SocketGuildUser); @@ -58,6 +66,8 @@ public async Task ShowTimeForUserComparedToCurrentUser(DateTime requestTime, str } [Command("time of")] + [Summary("Shows your local time for the specified message.")] + [Examples("time of 647141579345100840")] public async Task ShowTimeForMessage(ulong messageId) { var m = await Context.Channel.GetMessageAsync(messageId); @@ -69,6 +79,8 @@ public async Task ShowTimeForMessage(ulong messageId) } [Command("time set")] + [Summary("Set your current time offset.")] + [Examples("time set 16:17")] public async Task SetMyTimeOffset(DateTime currentTime) { var u = _entityConvertor.ConvertUser(Context.User as SocketGuildUser); @@ -78,6 +90,8 @@ public async Task SetMyTimeOffset(DateTime currentTime) [RequireUserPermission(GuildPermission.Administrator)] [Command("time set for")] + [Summary("Set a user's time offset.")] + [Examples("time set for @Draxis 00:00")] public async Task SetMyTimeOffset(MiunieUser user, DateTime currentTime) { var c = _entityConvertor.ConvertChannel(Context.Channel as SocketGuildChannel); diff --git a/src/Miunie.Discord/Embeds/EmbedConstructor.cs b/src/Miunie.Discord/Embeds/EmbedConstructor.cs index 323e5cf..651127a 100644 --- a/src/Miunie.Discord/Embeds/EmbedConstructor.cs +++ b/src/Miunie.Discord/Embeds/EmbedConstructor.cs @@ -17,6 +17,7 @@ using Miunie.Core.Entities; using Miunie.Core.Entities.Discord; using Miunie.Core.Providers; +using Miunie.Discord.Entities; using System; using System.Collections.Generic; using System.Linq; @@ -25,17 +26,35 @@ namespace Miunie.Discord.Embeds { internal static class EmbedConstructor { - private static readonly int RepLogPageSize = 10; + private static readonly int _repLogPageSize = 10; + private static readonly uint _miuniePinkColor = 0xEC407A; + + public static Embed CreateHelpEmbed(HelpResult result) + { + var builder = new EmbedBuilder().WithColor(_miuniePinkColor); + + if (!string.IsNullOrWhiteSpace(result.Title)) + { + _ = builder.WithTitle(result.Title); + } + + foreach (HelpSection section in result.Sections) + { + _ = builder.AddField(section.Title, section.Content, false); + } + + return builder.Build(); + } public static Embed CreateReputationLog(IEnumerable entries, int index, ILanguageProvider lang) { var embed = Paginator.PaginateEmbed( entries, new EmbedBuilder() - .WithColor(new Color(236, 64, 122)) + .WithColor(new Color(_miuniePinkColor)) .WithTitle(lang.GetPhrase(PhraseKey.USER_EMBED_REP_LOG_TITLE.ToString())), index, - RepLogPageSize, + _repLogPageSize, x => $"{(x.IsFromInvoker ? "**To:**" : "**From:**")} {x.TargetName} (**{FormatReputationType(x.Type)}**) {x.GivenAt:d} at {x.GivenAt:t} UTC"); if (string.IsNullOrWhiteSpace(embed.Description)) @@ -51,7 +70,7 @@ public static Embed ToEmbed(this MiunieUser mUser, ILanguageProvider lang) var realnessPhrase = lang.GetPhrase((mUser.IsBot ? PhraseKey.USER_EMBED_IS_BOT : PhraseKey.USER_EMBED_IS_HUMAN).ToString()); return new EmbedBuilder() - .WithColor(new Color(236, 64, 122)) + .WithColor(new Color(_miuniePinkColor)) .WithTitle(lang.GetPhrase(PhraseKey.USER_EMBED_TITLE.ToString())) .WithThumbnailUrl(mUser.AvatarUrl) .AddField(lang.GetPhrase(PhraseKey.USER_EMBED_NAME_TITLE.ToString()), mUser.Name) @@ -66,7 +85,7 @@ public static Embed ToEmbed(this MiunieUser mUser, ILanguageProvider lang) public static Embed ToEmbed(this MiunieGuild mGuild, ILanguageProvider lang) => new EmbedBuilder() - .WithColor(new Color(236, 64, 122)) + .WithColor(new Color(_miuniePinkColor)) .WithThumbnailUrl(mGuild.IconUrl) .WithTitle(lang.GetPhrase(PhraseKey.GUILD_EMBED_TITLE.ToString())) .AddField(lang.GetPhrase(PhraseKey.GUILD_EMBED_NAME_TITLE.ToString()), mGuild.Name) @@ -76,16 +95,11 @@ public static Embed ToEmbed(this MiunieGuild mGuild, ILanguageProvider lang) .Build(); private static string FormatReputationType(ReputationType type) - { - switch (type) + => type switch { - case ReputationType.Plus: - return "+1"; - case ReputationType.Minus: - return "-1"; - default: - throw new ArgumentException("Unknown ReputationType."); - } - } + ReputationType.Plus => "+1", + ReputationType.Minus => "-1", + _ => throw new ArgumentException("Unknown ReputationType.") + }; } } diff --git a/src/Miunie.Discord/Entities/HelpResult.cs b/src/Miunie.Discord/Entities/HelpResult.cs new file mode 100644 index 0000000..c4ae7dc --- /dev/null +++ b/src/Miunie.Discord/Entities/HelpResult.cs @@ -0,0 +1,26 @@ +// This file is part of Miunie. +// +// Miunie is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Miunie is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Miunie. If not, see . + +using System.Collections.Generic; + +namespace Miunie.Discord.Entities +{ + public class HelpResult + { + public string Title { get; set; } + + public IEnumerable Sections { get; set; } + } +} diff --git a/src/Miunie.Discord/Entities/HelpSection.cs b/src/Miunie.Discord/Entities/HelpSection.cs new file mode 100644 index 0000000..acdb591 --- /dev/null +++ b/src/Miunie.Discord/Entities/HelpSection.cs @@ -0,0 +1,30 @@ +// This file is part of Miunie. +// +// Miunie is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Miunie is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Miunie. If not, see . + +namespace Miunie.Discord.Entities +{ + public class HelpSection + { + public HelpSection(string title, string content) + { + Title = title; + Content = content; + } + + public string Title { get; } + + public string Content { get; } + } +} diff --git a/src/Miunie.Discord/Extensions/CommandInfoExtensions.cs b/src/Miunie.Discord/Extensions/CommandInfoExtensions.cs new file mode 100644 index 0000000..84fdc23 --- /dev/null +++ b/src/Miunie.Discord/Extensions/CommandInfoExtensions.cs @@ -0,0 +1,33 @@ +// This file is part of Miunie. +// +// Miunie is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Miunie is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Miunie. If not, see . + +using Discord.Commands; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Miunie.Discord +{ + internal static class CommandInfoExtensions + { + internal static TAttribute FindAttribute(this CommandInfo command) + where TAttribute : Attribute + => command.Attributes.FirstOrDefault(x => x is TAttribute) as TAttribute; + + internal static IEnumerable FindAttributes(this CommandInfo command) + where TAttribute : Attribute + => command.Attributes.Where(x => x is TAttribute).Select(x => x as TAttribute); + } +} diff --git a/src/Miunie.Discord/Miunie.Discord.csproj b/src/Miunie.Discord/Miunie.Discord.csproj index 6d337b1..75f6dd8 100644 --- a/src/Miunie.Discord/Miunie.Discord.csproj +++ b/src/Miunie.Discord/Miunie.Discord.csproj @@ -15,6 +15,7 @@ netstandard2.0 ../.ruleset + 8.0 diff --git a/src/Miunie.Discord/Providers/CommandHelpProvider.cs b/src/Miunie.Discord/Providers/CommandHelpProvider.cs new file mode 100644 index 0000000..c6b3121 --- /dev/null +++ b/src/Miunie.Discord/Providers/CommandHelpProvider.cs @@ -0,0 +1,129 @@ +// This file is part of Miunie. +// +// Miunie is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Miunie is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Miunie. If not, see . + +using Discord.Commands; +using Miunie.Core.Entities; +using Miunie.Core.Providers; +using Miunie.Discord.Attributes; +using Miunie.Discord.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Miunie.Discord +{ + public class CommandHelpProvider + { + private readonly CommandService _commandService; + private readonly ILanguageProvider _lang; + + public CommandHelpProvider(CommandService commandService, ILanguageProvider lang) + { + _commandService = commandService; + _lang = lang; + } + + public HelpResult ForAllCommands() + => new HelpResult + { + Title = _lang.GetPhrase(PhraseKey.USER_EMBED_HELP_TITLE.ToString()), + Sections = _commandService.Modules.Select(x => GetSection(x)) + }; + + public HelpResult FromInput(string input) + => new HelpResult() + { + Sections = GetCommands(input).Select(x => GetSection(x)) + }; + + private IEnumerable GetCommands(string input) + { + SearchResult result = _commandService.Search(input); + + if (!result.IsSuccess) + { + throw new Exception(result.ErrorReason); + } + + return result.Commands.Select(x => x.Command); + } + + private HelpSection GetSection(ModuleInfo module) + => new HelpSection(module.Name, GetModuleCommandBlocks(module)); + + private string GetModuleCommandBlocks(ModuleInfo module) + { + var commands = module.Commands + .GroupBy(x => x.Name) + .Select(x => $"`{x.Key}`"); + + return string.Join(" ", commands); + } + + private HelpSection GetSection(CommandInfo command) + { + string title = GetSectionTitle(command); + string summary = GetSummary(command); + string examples = GetExamples(command); + + var content = new StringBuilder(); + + _ = content.Append(_lang.GetPhrase(PhraseKey.HELP_SUMMARY_TITLE.ToString())) + .AppendLine(summary) + .Append(_lang.GetPhrase(PhraseKey.HELP_EXAMPLE_TITLE.ToString())) + .Append(examples); + + return new HelpSection(title, content.ToString()); + } + + private string GetSectionTitle(CommandInfo command) + => $"{command.Name} {GetSectionParameters(command)}"; + + private string GetSectionParameters(CommandInfo command) + { + var parameters = command.Parameters + .OrderBy(x => x.IsOptional) + .Select(x => GetParameterBlock(x)); + + return string.Join(" ", parameters); + } + + private string GetParameterBlock(ParameterInfo parameter) + => parameter.IsOptional ? $"[{parameter.Name}]" : $"<{parameter.Name}>"; + + private string GetExamples(CommandInfo command) + { + var examples = command.FindAttribute()?.Examples; + + if (examples is null || !examples.Any()) + { + return _lang.GetPhrase(PhraseKey.HELP_EXAMPLE_EMPTY.ToString()); + } + + return string.Join(", ", examples.Select(x => $"`{x}`")); + } + + private string GetSummary(CommandInfo command) + { + if (string.IsNullOrWhiteSpace(command.Summary)) + { + return _lang.GetPhrase(PhraseKey.HELP_SUMMARY_EMPTY.ToString()); + } + + return command.Summary; + } + } +} diff --git a/src/Miunie.Discord/TypeReaders/MiunieUserTypeReader.cs b/src/Miunie.Discord/TypeReaders/MiunieUserTypeReader.cs index a2ec0f7..fc8ec83 100644 --- a/src/Miunie.Discord/TypeReaders/MiunieUserTypeReader.cs +++ b/src/Miunie.Discord/TypeReaders/MiunieUserTypeReader.cs @@ -33,7 +33,7 @@ public MiunieUserTypeReader(EntityConvertor convertor) public override async Task ReadAsync(ICommandContext context, string input, IServiceProvider services) { - var discordUserId = MentionUtils.TryParseUser(input, out var userId); + _ = MentionUtils.TryParseUser(input, out var userId); if (await context.Guild.GetUserAsync(userId) is SocketGuildUser discordUser) { diff --git a/src/Miunie.InversionOfControl/Container.cs b/src/Miunie.InversionOfControl/Container.cs index 4d3f29f..1bd5453 100644 --- a/src/Miunie.InversionOfControl/Container.cs +++ b/src/Miunie.InversionOfControl/Container.cs @@ -13,6 +13,7 @@ // You should have received a copy of the GNU General Public License // along with Miunie. If not, see . +using Discord.Commands; using Microsoft.Extensions.DependencyInjection; using Miunie.Core.Attributes; using Miunie.Core.Configuration; @@ -47,6 +48,7 @@ public static IServiceCollection AddMiunieTypes(this IServiceCollection collecti .AddSingleton() .AddScoped() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddMiunieServices(); diff --git a/src/Miunie.InversionOfControl/Miunie.InversionOfControl.csproj b/src/Miunie.InversionOfControl/Miunie.InversionOfControl.csproj index d4f6013..298cc1c 100644 --- a/src/Miunie.InversionOfControl/Miunie.InversionOfControl.csproj +++ b/src/Miunie.InversionOfControl/Miunie.InversionOfControl.csproj @@ -3,6 +3,7 @@ netstandard2.0 ../.ruleset + 8.0 diff --git a/src/Miunie.LiteDbStorage/Miunie.LiteDbStorage.csproj b/src/Miunie.LiteDbStorage/Miunie.LiteDbStorage.csproj index 5341737..28407cc 100644 --- a/src/Miunie.LiteDbStorage/Miunie.LiteDbStorage.csproj +++ b/src/Miunie.LiteDbStorage/Miunie.LiteDbStorage.csproj @@ -1,12 +1,13 @@ - + netstandard2.0 ../.ruleset + 8.0 - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Miunie.Logger/Miunie.Logger.csproj b/src/Miunie.Logger/Miunie.Logger.csproj index 74b8040..e23d738 100644 --- a/src/Miunie.Logger/Miunie.Logger.csproj +++ b/src/Miunie.Logger/Miunie.Logger.csproj @@ -3,6 +3,7 @@ netstandard2.0 ../.ruleset + 8.0 diff --git a/src/Miunie.SystemInfrastructure/Miunie.SystemInfrastructure.csproj b/src/Miunie.SystemInfrastructure/Miunie.SystemInfrastructure.csproj index 0fec008..685d8a2 100644 --- a/src/Miunie.SystemInfrastructure/Miunie.SystemInfrastructure.csproj +++ b/src/Miunie.SystemInfrastructure/Miunie.SystemInfrastructure.csproj @@ -3,6 +3,7 @@ netstandard2.0 ../.ruleset + 8.0