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