Documentation Overhaul (#1161)
* Add XML docs
* Clean up style switcher
* Squash commits on branch docs/faq-n-patches
* Fix broken theme selector
* Add local image embed instruction
* Add a bunch of XML docs
* Add a bunch of XML docs
* Fix broken search
+ DocFX by default ships with an older version of jQuery, switching to a newer version confuses parts of the DocFX Javascript.
* Minor fixes for CONTRIBUTING.md and README.md
* Clean up filterConfig.yml
+ New config exposes Discord.Net namespace since it has several common public exceptions that may be helpful to users
* Add XML docs
* Read token from Environment Variable instead of hardcode
* Add XMLDocs
* Compress some assets & add OAuth2 URL generator
* Fix sample link & add missing pictures
* Add tag examples
* Fix embed docs consistency
* Add details regarding userbot support
* Add XML Docs
* Add XML Docs
* Add XML Docs
* Minor fixes in documentations
+ Fix unescaped '<'
+ Fix typo
* Fix seealso for preconditions and add missing descriptions
* Add missing exceptions
* Document exposed TypeReaders
* Fix letter-casing for files
* Add 'last modified' plugin
Source: https://github.com/Still34/DocFx.Plugin.LastModified
Licensed under MIT License
* XML Docs
* Fix minor consistencies & redundant impl
* Add properties examples to overwrite
* Fix missing Username prop
* Add warning for bulk-delete endpoint
* Replace note block
* Add BaseSocketClient docs
* Add XML docs
* Replace langword null to code block null instead
- Because DocFX sucks at rendering langword
* Replace all langword placements with code block
* Add more IGuild docs
* Add details to SpotifyGame
* Initial proofread of the articles
* Add explanation for RunMode
* Add event docs
- MessageReceived
- ChannelUpdated/Destroyed/Created
* Fix light theme link color
* Fix xml docs error
* Add partial documentation for audit log impl
* Add documentation for some REST-based objects
* Add partial documentation for audit log objects
* Add more XML comments to quotation mark alias map stuff, including an example
* Add reference to CommandServiceConfig from the util docs'
* Add explanation that if " is removed then it wont work
* Fix missing service provider in example
* Add documentation for new INestedChannel
* Add documentation
* Add documentation for new API version & few events
* Revise guide paragraphs/samples
+ Fix various formatting.
+ Provide a more detailed walkthrough for dependency injection.
+ Add C# note at intro.
* Fix typos & formatting
* Improve group module example
* Small amount to see if I'm doing it right
* Remove/cleanup redundant variables
* Fix EnterTypingState impl for doc inheritance
* Fix Test to resolve changes made in 15b58e
* Improve precondition documentation
+ Add precondition usage sample
+ Add precondition group usage sample
+ Move precondition samples to its own sample folder
* Move samples to individual folders
* Clarify token source
* Cleanup styling of README.md for docs
* Replace InvalidPathChars for NS1.3
* InvalidPathChars does not exist in NS1.3; replaced with GetInvalidPathChars instead.
* Add a missing change for 2c7cc738
* Update LastModified to v1.1.0 & add license
* Rewrite installation page for Core 2.1
* Fix anchor link
* Bump post-processor to v1.1.1
* Add fixes to partial file & add license
* Moved theme-switcher code to scripts partial file
+ Add author's MIT license to featherlight javascript
* Remove unused bootstrap plugin
* Bump LastModified plugin
* Changed the path from 'lastmodified' to 'last-modified' for consistency
* Cleanup README & Contribution guide
* Changes to last pr
* Fix GetCategoryAsync docs
* Proofread and cleanup articles
* Change passive voice in "Get Started" to active
* Fix improper preposition in Commands Introduction page
* Fix minor grammar mistakes in "Your First Bot" (future tense -> present tense/subjunctive mood -> indicative mood/proper noun casing/incorrect noun/add missing article)
* Fix minor grammar mistakes in "Installation" (missing article)
* no hablo ingles
* Try try try again
* I'm sure you're having as much fun as I am
* Cleanup TOC & fix titles
* Improve styling
+ Change title font to Noto Sans
+ Add materialized design for commit message box
* Add DescriptionGenerator plugin
* Add nightly section for clarification
* Fix typos in Nightlies & Post-execution
* Bump DescriptionGenerator to v1.1.0
+ This build adds the functionality of generating managed references' summary into the description tag.
* Initial emoji article draft
* Add 'additional information' section for emoji article
* Add cosmetic changes to the master css
* Alter info box color
+ Add transition to article content
* Add clarification in the emoji article
* Emphasize that normal emoji string will not translate to its Unicode representation.
* Clean up or add some of the samples featured in the article.
+ Add emoji/emote declaration section for clarification.
+ Add WebSocket emote sample.
- Remove inconsistent styling ('wacky memes' proves to be too out of place).
* Improve readability for nightlies article
* Move 'Bundled Preconditions' section
* Bump LastModified to fix UTC DateTime parsing
* Add langwordMapping.yml
* Add XML docs
* Add VSC workspace rule
* The root workspace limits the ruler to 120 characters for member documentations and excludes folders such as 'samples' and 'docs'.
* The docs workspace limits the ruler to 70 characters for standard conceptual article to comply with documentation's CONTRIBUTING.md rule, and excludes temprorary folders created by DocFX.
* Update CONTRIBUTING.md
* Add documentation style rule
* Fix styling of several member documentation
* Fix ' />' caused by Agent Smith oddities
* Fix styling to be more specific about the mention of IDs
* Fix exception summary to comply with official Microsoft Docs style
* References
https://docs.microsoft.com/en-us/dotnet/api/system.argumentnullexception?view=netframework-4.7.2
https://docs.microsoft.com/en-us/dotnet/api/system.platformnotsupportedexception?view=netframework-4.7.2
https://docs.microsoft.com/en-us/dotnet/api/system.badimageformatexception?view=netframework-4.7.2
* Add XML documentations
* Shift color return docs
* Fix minor docs
* Added documentation for SocketDMChannel, SocketGuildChannel, and SocketTextChannel
* Add XML docs
* Corrections to SocketGuildChannel
* Corrections to SocketTextChannel
* Corrections to SocketDMChannel
* Swapped out 'id' for 'snowflake identifier
* Swapped out 'id' for 'snowflake identifier'
* SocketDMChannel amendments
* SocketGuildChannel amendments
* SocketTextChannel amendments
* Add XML docs & patch return types
+ Starting from this commit, all return types for tasks will use style similar to most documentations featured on docs.microsoft.com
References:
https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbcontext.-ctor?view=efcore-2.1
https://docs.microsoft.com/en-us/dotnet/api/system.io.filestream.readasync?view=netcore-2.1
https://docs.microsoft.com/en-us/dotnet/api/system.io.textwriter.writelineasync?view=netcore-2.1#System_IO_TextWriter_WriteLineAsync_System_Char___
And many more other asynchronous method documentations featured in the latest BCL.
* Added documentation for many audit log data types, fixed vowel indefinite articles
* Change audit log data types to start with 'Contains' (verb) instead of an article
* Fix some documentation issues and document some more audit log data types
* Fix English posession
* Add XML doc
* Documented two more types
* Documented RoleCreateAuditLogData
* Document remaining audit log data types
* Added RestDMChannel documentation
* Added RestGuildChannel documentation
* Added RestTextChannel documentation
* Added RestVoiceChannel documentation
* Added RestUser documentation
* Added RestRole documentation
* Added RestMessage documentation
* Slightly better wording
* Contains -> Contains a piece of (describe article)
* [EN] Present perf. -> past perf.
* Add XML docs
* Fix arrow alignment
* Clarify supported nullable type
* Fixed a typo in ISnowflakeEntity
* Added RestUser Documentation
* Added RestInvite documentation
* Add XML docs & minor optimizations
* Minor optimization for doc rendering
* Rollback font optimization changes
* Amendments to RestUser
* Added SocketDMChannel documentation
* Added RestDMChannel documentation
* Added RestGuild documentation
* Adjustment to SocketDMChannel
* Added minimal descriptions from the API documentation for Integration types
* Added obsolete mention to the ReadMessages flag.
* Added remarks about 2FA requirement for guild permissions
* Added xmldoc for GuildPermission methods
* Added xml doc for ToAllowList and ToDenyList
* Added specification of how the bits of the color raw value are packed
* Added discord API documentation to IConnection interface
* I can spell :^)
* Fix whitespace in ChannelPermission
* fix spacing of values in guildpermission
* Made changes to get field descriptions from feedback, added returns tag to IConnection
* Added property get standard for IntegrationAccount
* Added property get pattern to xml docs and identical returns tag.
* Change all color class references to struct
...because it isn't a class.
* Add XML docs
* Rewrote the returns tags in IGuildIntegration, removed the ones I was unsure about.
* Rewrote the rest of the returns tags
* Amendments
* Cleanup doc for c1d78189
* Added types to <returns> tags where missing
* Added second sample for adding reactions
* Added some class summaries
* Missed a period
* Amendments
* restored the removed line break
* Removed unnecessary see tag
* Use consistent quotation marks around subscribers, the name for these users are dependant on the source of where they are integrated from (youtube or twitch), so we should not use a name that is specific to one platform
* Add <remarks> tag to the IGuildIntegration xmldocs
* Fix grammar issue
* Update DescriptionGenerator
* Cleanup of https://github.com/Still34/Discord.Net/pull/8
* Cleanup previous PR
* Fix for misleading behaviour in the emoji guide
+ Original lines stated that sending a emoji wrapped in colon will not be parsed, but that was incorrect; replaced with reactions instead of sending messages as the example
* Add strings for dictionary in DotSettings
* Add XML docs
* Fix lots of typos in comments
+ Geez, I didn't know there were so many.
* Add XML docs & rewrite GetMessagesAsync docs
This commit rewrites the remarks section of GetMessagesAsync, as well as adding examples to several methods.
* Update 'Your First Bot'
+ This commit reflects the new changes made to the Discord Application Developer Portal after its major update
* Initial optimization for DocFX render & add missing files
* Add examples in message methods
* Cleanup https://github.com/RogueException/Discord.Net/pull/1128
* Fix first bot note
* Cleanup FAQ structure
* Add XML docs
* Update docfx plugins
* Fix navbar collapsing issue
* Fix broken xref
* Cleanup FAQ section
+ Add introductory paragraphs to each FAQ section.
+ Add 'missing dependency' entry to commands FAQ.
* Split commands FAQ to 'General' and 'DI' sections.
* Cleanup https://github.com/RogueException/Discord.Net/pull/1139
* Fix missing namespace
* Add missing highlighting css for the light theme
* Add additional clarification for installing packages
* Add indentation to example for clarity
* Cleanup several articles to be more human-friendly and easier to read
* Remove RPC-related notes
* Cleanup slow-mode-related documentation strings
* Add an additional note about cross-guild emote usage
* Add CreateTextChannel sample
* Add XMLDocs
This commit is contained in:
@@ -2,14 +2,37 @@ using System;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary> Provides aliases for a command. </summary>
|
||||
/// <summary>
|
||||
/// Marks the aliases for a command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This attribute allows a command to have one or multiple aliases. In other words, the base command can have
|
||||
/// multiple aliases when triggering the command itself, giving the end-user more freedom of choices when giving
|
||||
/// hot-words to trigger the desired command. See the example for a better illustration.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// In the following example, the command can be triggered with the base name, "stats", or either "stat" or
|
||||
/// "info".
|
||||
/// <code language="cs">
|
||||
/// [Command("stats")]
|
||||
/// [Alias("stat", "info")]
|
||||
/// public async Task GetStatsAsync(IUser user)
|
||||
/// {
|
||||
/// // ...pull stats
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public class AliasAttribute : Attribute
|
||||
{
|
||||
/// <summary> The aliases which have been defined for the command. </summary>
|
||||
/// <summary>
|
||||
/// Gets the aliases which have been defined for the command.
|
||||
/// </summary>
|
||||
public string[] Aliases { get; }
|
||||
|
||||
/// <summary> Creates a new <see cref="AliasAttribute"/> with the given aliases. </summary>
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="AliasAttribute" /> with the given aliases.
|
||||
/// </summary>
|
||||
public AliasAttribute(params string[] aliases)
|
||||
{
|
||||
Aliases = aliases;
|
||||
|
||||
@@ -2,17 +2,32 @@ using System;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks the execution information for a command.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public class CommandAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the text that has been set to be recognized as a command.
|
||||
/// </summary>
|
||||
public string Text { get; }
|
||||
/// <summary>
|
||||
/// Specifies the <see cref="RunMode" /> of the command. This affects how the command is executed.
|
||||
/// </summary>
|
||||
public RunMode RunMode { get; set; } = RunMode.Default;
|
||||
public bool? IgnoreExtraArgs { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public CommandAttribute()
|
||||
{
|
||||
Text = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="CommandAttribute" /> attribute with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="text">The name of the command.</param>
|
||||
public CommandAttribute(string text)
|
||||
{
|
||||
Text = text;
|
||||
|
||||
@@ -2,6 +2,14 @@ using System;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Prevents the marked module from being loaded automatically.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This attribute tells <see cref="CommandService" /> to ignore the marked module from being loaded
|
||||
/// automatically (e.g. the <see cref="CommandService.AddModulesAsync" /> method). If a non-public module marked
|
||||
/// with this attribute is attempted to be loaded manually, the loading process will also fail.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
public class DontAutoLoadAttribute : Attribute
|
||||
{
|
||||
|
||||
@@ -1,9 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace Discord.Commands {
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||
public class DontInjectAttribute : Attribute {
|
||||
}
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Prevents the marked property from being injected into a module.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This attribute prevents the marked member from being injected into its parent module. Useful when you have a
|
||||
/// public property that you do not wish to invoke the library's dependency injection service.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// In the following example, <c>DatabaseService</c> will not be automatically injected into the module and will
|
||||
/// not throw an error message if the dependency fails to be resolved.
|
||||
/// <code language="cs">
|
||||
/// public class MyModule : ModuleBase
|
||||
/// {
|
||||
/// [DontInject]
|
||||
/// public DatabaseService DatabaseService;
|
||||
/// public MyModule()
|
||||
/// {
|
||||
/// DatabaseService = DatabaseFactory.Generate();
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||
public class DontInjectAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,26 @@ using System;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks the module as a command group.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
public class GroupAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the prefix set for the module.
|
||||
/// </summary>
|
||||
public string Prefix { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public GroupAttribute()
|
||||
{
|
||||
Prefix = null;
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="GroupAttribute" /> with the provided prefix.
|
||||
/// </summary>
|
||||
/// <param name="prefix">The prefix of the module group.</param>
|
||||
public GroupAttribute(string prefix)
|
||||
{
|
||||
Prefix = prefix;
|
||||
|
||||
@@ -3,11 +3,21 @@ using System;
|
||||
namespace Discord.Commands
|
||||
{
|
||||
// Override public name of command/module
|
||||
/// <summary>
|
||||
/// Marks the public name of a command, module, or parameter.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
||||
public class NameAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the command.
|
||||
/// </summary>
|
||||
public string Text { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Marks the public name of a command, module, or parameter with the provided name.
|
||||
/// </summary>
|
||||
/// <param name="text">The public name of the object.</param>
|
||||
public NameAttribute(string text)
|
||||
{
|
||||
Text = text;
|
||||
|
||||
@@ -4,17 +4,46 @@ using System.Reflection;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks the <see cref="Type"/> to be read by the specified <see cref="Discord.Commands.TypeReader"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This attribute will override the <see cref="Discord.Commands.TypeReader"/> to be used when parsing for the
|
||||
/// desired type in the command. This is useful when one wishes to use a particular
|
||||
/// <see cref="Discord.Commands.TypeReader"/> without affecting other commands that are using the same target
|
||||
/// type.
|
||||
/// <note type="warning">
|
||||
/// If the given type reader does not inherit from <see cref="Discord.Commands.TypeReader"/>, an
|
||||
/// <see cref="ArgumentException"/> will be thrown.
|
||||
/// </note>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// In this example, the <see cref="TimeSpan"/> will be read by a custom
|
||||
/// <see cref="Discord.Commands.TypeReader"/>, <c>FriendlyTimeSpanTypeReader</c>, instead of the
|
||||
/// <see cref="TimeSpanTypeReader"/> shipped by Discord.Net.
|
||||
/// <code language="cs">
|
||||
/// [Command("time")]
|
||||
/// public Task GetTimeAsync([OverrideTypeReader(typeof(FriendlyTimeSpanTypeReader))]TimeSpan time)
|
||||
/// => ReplyAsync(time);
|
||||
/// </code>
|
||||
/// </example>
|
||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
||||
public class OverrideTypeReaderAttribute : Attribute
|
||||
{
|
||||
private static readonly TypeInfo TypeReaderTypeInfo = typeof(TypeReader).GetTypeInfo();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified <see cref="TypeReader"/> of the parameter.
|
||||
/// </summary>
|
||||
public Type TypeReader { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <param name="overridenTypeReader">The <see cref="TypeReader"/> to be used with the parameter. </param>
|
||||
/// <exception cref="ArgumentException">The given <paramref name="overridenTypeReader"/> does not inherit from <see cref="TypeReader"/>.</exception>
|
||||
public OverrideTypeReaderAttribute(Type overridenTypeReader)
|
||||
{
|
||||
if (!TypeReaderTypeInfo.IsAssignableFrom(overridenTypeReader.GetTypeInfo()))
|
||||
throw new ArgumentException($"{nameof(overridenTypeReader)} must inherit from {nameof(TypeReader)}");
|
||||
throw new ArgumentException($"{nameof(overridenTypeReader)} must inherit from {nameof(TypeReader)}.");
|
||||
|
||||
TypeReader = overridenTypeReader;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,20 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Requires the parameter to pass the specified precondition before execution can begin.
|
||||
/// </summary>
|
||||
/// <seealso cref="PreconditionAttribute"/>
|
||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)]
|
||||
public abstract class ParameterPreconditionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks whether the condition is met before execution of the command.
|
||||
/// </summary>
|
||||
/// <param name="context">The context of the command.</param>
|
||||
/// <param name="parameter">The parameter of the command being checked against.</param>
|
||||
/// <param name="value">The raw value of the parameter.</param>
|
||||
/// <param name="services">The service collection used for dependency injection.</param>
|
||||
public abstract Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, ParameterInfo parameter, object value, IServiceProvider services);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Requires the module or class to pass the specified precondition before execution can begin.
|
||||
/// </summary>
|
||||
/// <seealso cref="ParameterPreconditionAttribute"/>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
|
||||
public abstract class PreconditionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Specify a group that this precondition belongs to. Preconditions of the same group require only one
|
||||
/// of the preconditions to pass in order to be successful (A || B). Specifying <see cref="Group"/> = <see langword="null"/>
|
||||
/// or not at all will require *all* preconditions to pass, just like normal (A && B).
|
||||
/// Specifies a group that this precondition belongs to.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="Preconditions" /> of the same group require only one of the preconditions to pass in order to
|
||||
/// be successful (A || B). Specifying <see cref="Group" /> = <c>null</c> or not at all will
|
||||
/// require *all* preconditions to pass, just like normal (A && B).
|
||||
/// </remarks>
|
||||
public string Group { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the <paramref name="command"/> has the sufficient permission to be executed.
|
||||
/// </summary>
|
||||
/// <param name="context">The context of the command.</param>
|
||||
/// <param name="command">The command being executed.</param>
|
||||
/// <param name="services">The service collection used for dependency injection.</param>
|
||||
public abstract Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,52 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// This attribute requires that the bot has a specified permission in the channel a command is invoked in.
|
||||
/// Requires the bot to have a specific permission in the channel a command is invoked in.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
|
||||
public class RequireBotPermissionAttribute : PreconditionAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the specified <see cref="Discord.GuildPermission" /> of the precondition.
|
||||
/// </summary>
|
||||
public GuildPermission? GuildPermission { get; }
|
||||
/// <summary>
|
||||
/// Gets the specified <see cref="Discord.ChannelPermission" /> of the precondition.
|
||||
/// </summary>
|
||||
public ChannelPermission? ChannelPermission { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Require that the bot account has a specified GuildPermission
|
||||
/// Requires the bot account to have a specific <see cref="Discord.GuildPermission"/>.
|
||||
/// </summary>
|
||||
/// <remarks>This precondition will always fail if the command is being invoked in a private channel.</remarks>
|
||||
/// <param name="permission">The GuildPermission that the bot must have. Multiple permissions can be specified by ORing the permissions together.</param>
|
||||
/// <remarks>
|
||||
/// This precondition will always fail if the command is being invoked in a <see cref="IPrivateChannel"/>.
|
||||
/// </remarks>
|
||||
/// <param name="permission">
|
||||
/// The <see cref="Discord.GuildPermission"/> that the bot must have. Multiple permissions can be specified
|
||||
/// by ORing the permissions together.
|
||||
/// </param>
|
||||
public RequireBotPermissionAttribute(GuildPermission permission)
|
||||
{
|
||||
GuildPermission = permission;
|
||||
ChannelPermission = null;
|
||||
}
|
||||
/// <summary>
|
||||
/// Require that the bot account has a specified ChannelPermission.
|
||||
/// Requires that the bot account to have a specific <see cref="Discord.ChannelPermission"/>.
|
||||
/// </summary>
|
||||
/// <param name="permission">The ChannelPermission that the bot must have. Multiple permissions can be specified by ORing the permissions together.</param>
|
||||
/// <example>
|
||||
/// <code language="c#">
|
||||
/// [Command("permission")]
|
||||
/// [RequireBotPermission(ChannelPermission.ManageMessages)]
|
||||
/// public async Task Purge()
|
||||
/// {
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="permission">
|
||||
/// The <see cref="Discord.ChannelPermission"/> that the bot must have. Multiple permissions can be
|
||||
/// specified by ORing the permissions together.
|
||||
/// </param>
|
||||
public RequireBotPermissionAttribute(ChannelPermission permission)
|
||||
{
|
||||
ChannelPermission = permission;
|
||||
GuildPermission = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
||||
{
|
||||
IGuildUser guildUser = null;
|
||||
@@ -50,9 +56,9 @@ namespace Discord.Commands
|
||||
if (GuildPermission.HasValue)
|
||||
{
|
||||
if (guildUser == null)
|
||||
return PreconditionResult.FromError("Command must be used in a guild channel");
|
||||
return PreconditionResult.FromError("Command must be used in a guild channel.");
|
||||
if (!guildUser.GuildPermissions.Has(GuildPermission.Value))
|
||||
return PreconditionResult.FromError($"Bot requires guild permission {GuildPermission.Value}");
|
||||
return PreconditionResult.FromError($"Bot requires guild permission {GuildPermission.Value}.");
|
||||
}
|
||||
|
||||
if (ChannelPermission.HasValue)
|
||||
@@ -64,7 +70,7 @@ namespace Discord.Commands
|
||||
perms = ChannelPermissions.All(context.Channel);
|
||||
|
||||
if (!perms.Has(ChannelPermission.Value))
|
||||
return PreconditionResult.FromError($"Bot requires channel permission {ChannelPermission.Value}");
|
||||
return PreconditionResult.FromError($"Bot requires channel permission {ChannelPermission.Value}.");
|
||||
}
|
||||
|
||||
return PreconditionResult.FromSuccess();
|
||||
|
||||
@@ -1,35 +1,48 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the type of command context (i.e. where the command is being executed).
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ContextType
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the command to be executed within a guild.
|
||||
/// </summary>
|
||||
Guild = 0x01,
|
||||
/// <summary>
|
||||
/// Specifies the command to be executed within a DM.
|
||||
/// </summary>
|
||||
DM = 0x02,
|
||||
/// <summary>
|
||||
/// Specifies the command to be executed within a group.
|
||||
/// </summary>
|
||||
Group = 0x04
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Require that the command be invoked in a specified context.
|
||||
/// Requires the command to be invoked in a specified context (e.g. in guild, DM).
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
|
||||
public class RequireContextAttribute : PreconditionAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the context required to execute the command.
|
||||
/// </summary>
|
||||
public ContextType Contexts { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Require that the command be invoked in a specified context.
|
||||
/// </summary>
|
||||
/// <summary> Requires the command to be invoked in the specified context. </summary>
|
||||
/// <param name="contexts">The type of context the command can be invoked in. Multiple contexts can be specified by ORing the contexts together.</param>
|
||||
/// <example>
|
||||
/// <code language="c#">
|
||||
/// [Command("private_only")]
|
||||
/// <code language="cs">
|
||||
/// [Command("secret")]
|
||||
/// [RequireContext(ContextType.DM | ContextType.Group)]
|
||||
/// public async Task PrivateOnly()
|
||||
/// public Task PrivateOnlyAsync()
|
||||
/// {
|
||||
/// return ReplyAsync("shh, this command is a secret");
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
@@ -38,12 +51,13 @@ namespace Discord.Commands
|
||||
Contexts = contexts;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
||||
{
|
||||
bool isValid = false;
|
||||
|
||||
if ((Contexts & ContextType.Guild) != 0)
|
||||
isValid = isValid || context.Channel is IGuildChannel;
|
||||
isValid = context.Channel is IGuildChannel;
|
||||
if ((Contexts & ContextType.DM) != 0)
|
||||
isValid = isValid || context.Channel is IDMChannel;
|
||||
if ((Contexts & ContextType.Group) != 0)
|
||||
@@ -52,7 +66,7 @@ namespace Discord.Commands
|
||||
if (isValid)
|
||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||
else
|
||||
return Task.FromResult(PreconditionResult.FromError($"Invalid context for command; accepted contexts: {Contexts}"));
|
||||
return Task.FromResult(PreconditionResult.FromError($"Invalid context for command; accepted contexts: {Contexts}."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,33 @@ using System.Threading.Tasks;
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Require that the command is invoked in a channel marked NSFW
|
||||
/// Requires the command to be invoked in a channel marked NSFW.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The precondition will restrict the access of the command or module to be accessed within a guild channel
|
||||
/// that has been marked as mature or NSFW. If the channel is not of type <see cref="ITextChannel"/> or the
|
||||
/// channel is not marked as NSFW, the precondition will fail with an erroneous <see cref="PreconditionResult"/>.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// The following example restricts the command <c>too-cool</c> to an NSFW-enabled channel only.
|
||||
/// <code language="cs">
|
||||
/// public class DankModule : ModuleBase
|
||||
/// {
|
||||
/// [Command("cool")]
|
||||
/// public Task CoolAsync()
|
||||
/// => ReplyAsync("I'm cool for everyone.");
|
||||
///
|
||||
/// [RequireNsfw]
|
||||
/// [Command("too-cool")]
|
||||
/// public Task TooCoolAsync()
|
||||
/// => ReplyAsync("You can only see this if you're cool enough.");
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
|
||||
public class RequireNsfwAttribute : PreconditionAttribute
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
||||
{
|
||||
if (context.Channel is ITextChannel text && text.IsNsfw)
|
||||
|
||||
@@ -4,20 +4,45 @@ using System.Threading.Tasks;
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Require that the command is invoked by the owner of the bot.
|
||||
/// Requires the command to be invoked by the owner of the bot.
|
||||
/// </summary>
|
||||
/// <remarks>This precondition will only work if the bot is a bot account.</remarks>
|
||||
/// <remarks>
|
||||
/// This precondition will restrict the access of the command or module to the owner of the Discord application.
|
||||
/// If the precondition fails to be met, an erroneous <see cref="PreconditionResult"/> will be returned with the
|
||||
/// message "Command can only be run by the owner of the bot."
|
||||
/// <note>
|
||||
/// This precondition will only work if the account has a <see cref="TokenType"/> of <see cref="TokenType.Bot"/>
|
||||
/// ;otherwise, this precondition will always fail.
|
||||
/// </note>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// The following example restricts the command to a set of sensitive commands that only the owner of the bot
|
||||
/// application should be able to access.
|
||||
/// <code language="cs">
|
||||
/// [RequireOwner]
|
||||
/// [Group("admin")]
|
||||
/// public class AdminModule : ModuleBase
|
||||
/// {
|
||||
/// [Command("exit")]
|
||||
/// public async Task ExitAsync()
|
||||
/// {
|
||||
/// Environment.Exit(0);
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
|
||||
public class RequireOwnerAttribute : PreconditionAttribute
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override async Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
||||
{
|
||||
switch (context.Client.TokenType)
|
||||
{
|
||||
case TokenType.Bot:
|
||||
var application = await context.Client.GetApplicationInfoAsync();
|
||||
var application = await context.Client.GetApplicationInfoAsync().ConfigureAwait(false);
|
||||
if (context.User.Id != application.Owner.Id)
|
||||
return PreconditionResult.FromError("Command can only be run by the owner of the bot");
|
||||
return PreconditionResult.FromError("Command can only be run by the owner of the bot.");
|
||||
return PreconditionResult.FromSuccess();
|
||||
default:
|
||||
return PreconditionResult.FromError($"{nameof(RequireOwnerAttribute)} is not supported by this {nameof(TokenType)}.");
|
||||
|
||||
@@ -1,47 +1,52 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// This attribute requires that the user invoking the command has a specified permission.
|
||||
/// Requires the user invoking the command to have a specified permission.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
|
||||
public class RequireUserPermissionAttribute : PreconditionAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the specified <see cref="Discord.GuildPermission" /> of the precondition.
|
||||
/// </summary>
|
||||
public GuildPermission? GuildPermission { get; }
|
||||
/// <summary>
|
||||
/// Gets the specified <see cref="Discord.ChannelPermission" /> of the precondition.
|
||||
/// </summary>
|
||||
public ChannelPermission? ChannelPermission { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Require that the user invoking the command has a specified GuildPermission
|
||||
/// Requires that the user invoking the command to have a specific <see cref="Discord.GuildPermission"/>.
|
||||
/// </summary>
|
||||
/// <remarks>This precondition will always fail if the command is being invoked in a private channel.</remarks>
|
||||
/// <param name="permission">The GuildPermission that the user must have. Multiple permissions can be specified by ORing the permissions together.</param>
|
||||
/// <remarks>
|
||||
/// This precondition will always fail if the command is being invoked in a <see cref="IPrivateChannel"/>.
|
||||
/// </remarks>
|
||||
/// <param name="permission">
|
||||
/// The <see cref="Discord.GuildPermission" /> that the user must have. Multiple permissions can be
|
||||
/// specified by ORing the permissions together.
|
||||
/// </param>
|
||||
public RequireUserPermissionAttribute(GuildPermission permission)
|
||||
{
|
||||
GuildPermission = permission;
|
||||
ChannelPermission = null;
|
||||
}
|
||||
/// <summary>
|
||||
/// Require that the user invoking the command has a specified ChannelPermission.
|
||||
/// Requires that the user invoking the command to have a specific <see cref="Discord.ChannelPermission"/>.
|
||||
/// </summary>
|
||||
/// <param name="permission">The ChannelPermission that the user must have. Multiple permissions can be specified by ORing the permissions together.</param>
|
||||
/// <example>
|
||||
/// <code language="c#">
|
||||
/// [Command("permission")]
|
||||
/// [RequireUserPermission(ChannelPermission.ReadMessageHistory | ChannelPermission.ReadMessages)]
|
||||
/// public async Task HasPermission()
|
||||
/// {
|
||||
/// await ReplyAsync("You can read messages and the message history!");
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="permission">
|
||||
/// The <see cref="Discord.ChannelPermission"/> that the user must have. Multiple permissions can be
|
||||
/// specified by ORing the permissions together.
|
||||
/// </param>
|
||||
public RequireUserPermissionAttribute(ChannelPermission permission)
|
||||
{
|
||||
ChannelPermission = permission;
|
||||
GuildPermission = null;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
||||
{
|
||||
var guildUser = context.User as IGuildUser;
|
||||
@@ -49,9 +54,9 @@ namespace Discord.Commands
|
||||
if (GuildPermission.HasValue)
|
||||
{
|
||||
if (guildUser == null)
|
||||
return Task.FromResult(PreconditionResult.FromError("Command must be used in a guild channel"));
|
||||
return Task.FromResult(PreconditionResult.FromError("Command must be used in a guild channel."));
|
||||
if (!guildUser.GuildPermissions.Has(GuildPermission.Value))
|
||||
return Task.FromResult(PreconditionResult.FromError($"User requires guild permission {GuildPermission.Value}"));
|
||||
return Task.FromResult(PreconditionResult.FromError($"User requires guild permission {GuildPermission.Value}."));
|
||||
}
|
||||
|
||||
if (ChannelPermission.HasValue)
|
||||
@@ -63,7 +68,7 @@ namespace Discord.Commands
|
||||
perms = ChannelPermissions.All(context.Channel);
|
||||
|
||||
if (!perms.Has(ChannelPermission.Value))
|
||||
return Task.FromResult(PreconditionResult.FromError($"User requires channel permission {ChannelPermission.Value}"));
|
||||
return Task.FromResult(PreconditionResult.FromError($"User requires channel permission {ChannelPermission.Value}."));
|
||||
}
|
||||
|
||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||
|
||||
@@ -2,14 +2,20 @@ using System;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary> Sets priority of commands </summary>
|
||||
/// <summary>
|
||||
/// Sets priority of commands.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public class PriorityAttribute : Attribute
|
||||
{
|
||||
/// <summary> The priority which has been set for the command </summary>
|
||||
/// <summary>
|
||||
/// Gets the priority which has been set for the command.
|
||||
/// </summary>
|
||||
public int Priority { get; }
|
||||
|
||||
/// <summary> Creates a new <see cref="PriorityAttribute"/> with the given priority. </summary>
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="PriorityAttribute" /> attribute with the given priority.
|
||||
/// </summary>
|
||||
public PriorityAttribute(int priority)
|
||||
{
|
||||
Priority = priority;
|
||||
|
||||
@@ -2,6 +2,9 @@ using System;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks the input to not be parsed by the parser.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
||||
public class RemainderAttribute : Attribute
|
||||
{
|
||||
|
||||
@@ -3,6 +3,9 @@ using System;
|
||||
namespace Discord.Commands
|
||||
{
|
||||
// Extension of the Cosmetic Summary, for Groups, Commands, and Parameters
|
||||
/// <summary>
|
||||
/// Attaches remarks to your commands.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
public class RemarksAttribute : Attribute
|
||||
{
|
||||
|
||||
@@ -3,6 +3,9 @@ using System;
|
||||
namespace Discord.Commands
|
||||
{
|
||||
// Cosmetic Summary, for Groups and Commands
|
||||
/// <summary>
|
||||
/// Attaches a summary to your command.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
||||
public class SummaryAttribute : Attribute
|
||||
{
|
||||
|
||||
@@ -118,6 +118,7 @@ namespace Discord.Commands.Builders
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <exception cref="InvalidOperationException">Only the last parameter in a command may have the Remainder or Multiple flag.</exception>
|
||||
internal CommandInfo Build(ModuleInfo info, CommandService service)
|
||||
{
|
||||
//Default name to primary alias
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Discord.Commands
|
||||
}
|
||||
else if (IsLoadableModule(typeInfo))
|
||||
{
|
||||
await service._cmdLogger.WarningAsync($"Class {typeInfo.FullName} is not public and cannot be loaded. To suppress this message, mark the class with {nameof(DontAutoLoadAttribute)}.");
|
||||
await service._cmdLogger.WarningAsync($"Class {typeInfo.FullName} is not public and cannot be loaded. To suppress this message, mark the class with {nameof(DontAutoLoadAttribute)}.").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
namespace Discord.Commands
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary> The context of a command which may contain the client, user, guild, channel, and message. </summary>
|
||||
public class CommandContext : ICommandContext
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public IDiscordClient Client { get; }
|
||||
/// <inheritdoc/>
|
||||
public IGuild Guild { get; }
|
||||
/// <inheritdoc/>
|
||||
public IMessageChannel Channel { get; }
|
||||
/// <inheritdoc/>
|
||||
public IUser User { get; }
|
||||
/// <inheritdoc/>
|
||||
public IUserMessage Message { get; }
|
||||
|
||||
/// <summary> Indicates whether the channel that the command is executed in is a private channel. </summary>
|
||||
public bool IsPrivate => Channel is IPrivateChannel;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="CommandContext" /> class with the provided client and message.
|
||||
/// </summary>
|
||||
/// <param name="client">The underlying client.</param>
|
||||
/// <param name="msg">The underlying message.</param>
|
||||
public CommandContext(IDiscordClient client, IUserMessage msg)
|
||||
{
|
||||
Client = client;
|
||||
|
||||
@@ -1,26 +1,51 @@
|
||||
namespace Discord.Commands
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary> Defines the type of error a command can throw. </summary>
|
||||
public enum CommandError
|
||||
{
|
||||
//Search
|
||||
/// <summary>
|
||||
/// Thrown when the command is unknown.
|
||||
/// </summary>
|
||||
UnknownCommand = 1,
|
||||
|
||||
//Parse
|
||||
/// <summary>
|
||||
/// Thrown when the command fails to be parsed.
|
||||
/// </summary>
|
||||
ParseFailed,
|
||||
/// <summary>
|
||||
/// Thrown when the input text has too few or too many arguments.
|
||||
/// </summary>
|
||||
BadArgCount,
|
||||
|
||||
//Parse (Type Reader)
|
||||
//CastFailed,
|
||||
/// <summary>
|
||||
/// Thrown when the object cannot be found by the <see cref="TypeReader"/>.
|
||||
/// </summary>
|
||||
ObjectNotFound,
|
||||
/// <summary>
|
||||
/// Thrown when more than one object is matched by <see cref="TypeReader"/>.
|
||||
/// </summary>
|
||||
MultipleMatches,
|
||||
|
||||
//Preconditions
|
||||
/// <summary>
|
||||
/// Thrown when the command fails to meet a <see cref="PreconditionAttribute"/>'s conditions.
|
||||
/// </summary>
|
||||
UnmetPrecondition,
|
||||
|
||||
//Execute
|
||||
/// <summary>
|
||||
/// Thrown when an exception occurs mid-command execution.
|
||||
/// </summary>
|
||||
Exception,
|
||||
|
||||
//Runtime
|
||||
/// <summary>
|
||||
/// Thrown when the command is not successfully executed on runtime.
|
||||
/// </summary>
|
||||
Unsuccessful
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,24 @@ using System;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// The exception that is thrown if another exception occurs during a command execution.
|
||||
/// </summary>
|
||||
public class CommandException : Exception
|
||||
{
|
||||
/// <summary> Gets the command that caused the exception. </summary>
|
||||
public CommandInfo Command { get; }
|
||||
/// <summary> Gets the command context of the exception. </summary>
|
||||
public ICommandContext Context { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CommandException" /> class using a
|
||||
/// <paramref name="command"/> information, a <paramref name="command"/> context, and the exception that
|
||||
/// interrupted the execution.
|
||||
/// </summary>
|
||||
/// <param name="command">The command information.</param>
|
||||
/// <param name="context">The context of the command.</param>
|
||||
/// <param name="ex">The exception that interrupted the command execution.</param>
|
||||
public CommandException(CommandInfo command, ICommandContext context, Exception ex)
|
||||
: base($"Error occurred executing {command.GetLogText(context)}.", ex)
|
||||
{
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
public struct CommandMatch
|
||||
{
|
||||
/// <summary> The command that matches the search result. </summary>
|
||||
public CommandInfo Command { get; }
|
||||
/// <summary> The alias of the command. </summary>
|
||||
public string Alias { get; }
|
||||
|
||||
public CommandMatch(CommandInfo command, string alias)
|
||||
|
||||
@@ -170,7 +170,7 @@ namespace Discord.Commands
|
||||
if (isEscaping)
|
||||
return ParseResult.FromError(CommandError.ParseFailed, "Input text may not end on an incomplete escape.");
|
||||
if (curPart == ParserPart.QuotedParameter)
|
||||
return ParseResult.FromError(CommandError.ParseFailed, "A quoted parameter is incomplete");
|
||||
return ParseResult.FromError(CommandError.ParseFailed, "A quoted parameter is incomplete.");
|
||||
|
||||
//Add missing optionals
|
||||
for (int i = argList.Count; i < command.Parameters.Count; i++)
|
||||
|
||||
@@ -11,11 +11,44 @@ using Discord.Logging;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a framework for building Discord commands.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The service provides a framework for building Discord commands both dynamically via runtime builders or
|
||||
/// statically via compile-time modules. To create a command module at compile-time, see
|
||||
/// <see cref="ModuleBase" /> (most common); otherwise, see <see cref="ModuleBuilder" />.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This service also provides several events for monitoring command usages; such as
|
||||
/// <see cref="Discord.Commands.CommandService.Log" /> for any command-related log events, and
|
||||
/// <see cref="Discord.Commands.CommandService.CommandExecuted" /> for information about commands that have
|
||||
/// been successfully executed.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class CommandService
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when a command-related information is received.
|
||||
/// </summary>
|
||||
public event Func<LogMessage, Task> Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } }
|
||||
internal readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>();
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a command is successfully executed without any error.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This event is fired when a command has been successfully executed without any of the following errors:
|
||||
/// </para>
|
||||
/// <para>* Parsing error</para>
|
||||
/// <para>* Precondition error</para>
|
||||
/// <para>* Runtime exception</para>
|
||||
/// <para>
|
||||
/// Should the command encounter any of the aforementioned error, this event will not be raised.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public event Func<CommandInfo, ICommandContext, IResult, Task> CommandExecuted { add { _commandExecutedEvent.Add(value); } remove { _commandExecutedEvent.Remove(value); } }
|
||||
internal readonly AsyncEvent<Func<CommandInfo, ICommandContext, IResult, Task>> _commandExecutedEvent = new AsyncEvent<Func<CommandInfo, ICommandContext, IResult, Task>>();
|
||||
|
||||
@@ -34,11 +67,33 @@ namespace Discord.Commands
|
||||
internal readonly LogManager _logManager;
|
||||
internal readonly IReadOnlyDictionary<char, char> _quotationMarkAliasMap;
|
||||
|
||||
/// <summary>
|
||||
/// Represents all modules loaded within <see cref="CommandService"/>.
|
||||
/// </summary>
|
||||
public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x);
|
||||
|
||||
/// <summary>
|
||||
/// Represents all commands loaded within <see cref="CommandService"/>.
|
||||
/// </summary>
|
||||
public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Commands);
|
||||
|
||||
/// <summary>
|
||||
/// Represents all <see cref="TypeReader" /> loaded within <see cref="CommandService"/>.
|
||||
/// </summary>
|
||||
public ILookup<Type, TypeReader> TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new { y.Key, y.Value })).ToLookup(x => x.Key, x => x.Value);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="CommandService"/> class.
|
||||
/// </summary>
|
||||
public CommandService() : this(new CommandServiceConfig()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="CommandService"/> class with the provided configuration.
|
||||
/// </summary>
|
||||
/// <param name="config">The configuration class.</param>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// The <see cref="RunMode"/> cannot be set to <see cref="RunMode.Default"/>.
|
||||
/// </exception>
|
||||
public CommandService(CommandServiceConfig config)
|
||||
{
|
||||
_caseSensitive = config.CaseSensitiveCommands;
|
||||
@@ -102,12 +157,39 @@ namespace Discord.Commands
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a command module from a type
|
||||
/// Add a command module from a <see cref="Type" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of module</typeparam>
|
||||
/// <param name="services">An IServiceProvider for your dependency injection solution, if using one - otherwise, pass null</param>
|
||||
/// <returns>A built module</returns>
|
||||
/// <example>
|
||||
/// <para>The following example registers the module <c>MyModule</c> to <c>commandService</c>.</para>
|
||||
/// <code language="cs">
|
||||
/// await commandService.AddModuleAsync<MyModule>(serviceProvider);
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <typeparam name="T">The type of module.</typeparam>
|
||||
/// <param name="services">The <see cref="IServiceProvider"/> for your dependency injection solution if using one; otherwise, pass <c>null</c>.</param>
|
||||
/// <exception cref="ArgumentException">This module has already been added.</exception>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// The <see cref="ModuleInfo"/> fails to be built; an invalid type may have been provided.
|
||||
/// </exception>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous operation for adding the module. The task result contains the
|
||||
/// built module.
|
||||
/// </returns>
|
||||
public Task<ModuleInfo> AddModuleAsync<T>(IServiceProvider services) => AddModuleAsync(typeof(T), services);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a command module from a <see cref="Type" />.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of module.</param>
|
||||
/// <param name="services">The <see cref="IServiceProvider" /> for your dependency injection solution if using one; otherwise, pass <c>null</c> .</param>
|
||||
/// <exception cref="ArgumentException">This module has already been added.</exception>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// The <see cref="ModuleInfo"/> fails to be built; an invalid type may have been provided.
|
||||
/// </exception>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous operation for adding the module. The task result contains the
|
||||
/// built module.
|
||||
/// </returns>
|
||||
public async Task<ModuleInfo> AddModuleAsync(Type type, IServiceProvider services)
|
||||
{
|
||||
services = services ?? EmptyServiceProvider.Instance;
|
||||
@@ -135,11 +217,14 @@ namespace Discord.Commands
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Add command modules from an assembly
|
||||
/// Add command modules from an <see cref="Assembly"/>.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The assembly containing command modules</param>
|
||||
/// <param name="services">An IServiceProvider for your dependency injection solution, if using one - otherwise, pass null</param>
|
||||
/// <returns>A collection of built modules</returns>
|
||||
/// <param name="assembly">The <see cref="Assembly"/> containing command modules.</param>
|
||||
/// <param name="services">The <see cref="IServiceProvider"/> for your dependency injection solution if using one; otherwise, pass <c>null</c>.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous operation for adding the command modules. The task result
|
||||
/// contains an enumerable collection of modules added.
|
||||
/// </returns>
|
||||
public async Task<IEnumerable<ModuleInfo>> AddModulesAsync(Assembly assembly, IServiceProvider services)
|
||||
{
|
||||
services = services ?? EmptyServiceProvider.Instance;
|
||||
@@ -175,7 +260,14 @@ namespace Discord.Commands
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the command module.
|
||||
/// </summary>
|
||||
/// <param name="module">The <see cref="ModuleInfo" /> to be removed from the service.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous removal operation. The task result contains a value that
|
||||
/// indicates whether the <paramref name="module"/> is successfully removed.
|
||||
/// </returns>
|
||||
public async Task<bool> RemoveModuleAsync(ModuleInfo module)
|
||||
{
|
||||
await _moduleLock.WaitAsync().ConfigureAwait(false);
|
||||
@@ -188,7 +280,23 @@ namespace Discord.Commands
|
||||
_moduleLock.Release();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes the command module.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The <see cref="Type"/> of the module.</typeparam>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous removal operation. The task result contains a value that
|
||||
/// indicates whether the module is successfully removed.
|
||||
/// </returns>
|
||||
public Task<bool> RemoveModuleAsync<T>() => RemoveModuleAsync(typeof(T));
|
||||
/// <summary>
|
||||
/// Removes the command module.
|
||||
/// </summary>
|
||||
/// <param name="type">The <see cref="Type"/> of the module.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous removal operation. The task result contains a value that
|
||||
/// indicates whether the module is successfully removed.
|
||||
/// </returns>
|
||||
public async Task<bool> RemoveModuleAsync(Type type)
|
||||
{
|
||||
await _moduleLock.WaitAsync().ConfigureAwait(false);
|
||||
@@ -222,21 +330,27 @@ namespace Discord.Commands
|
||||
|
||||
//Type Readers
|
||||
/// <summary>
|
||||
/// Adds a custom <see cref="TypeReader"/> to this <see cref="CommandService"/> for the supplied object type.
|
||||
/// If <typeparamref name="T"/> is a <see cref="ValueType"/>, a <see cref="NullableTypeReader{T}"/> will also be added.
|
||||
/// If a default <see cref="TypeReader"/> exists for <typeparamref name="T"/>, a warning will be logged and the default <see cref="TypeReader"/> will be replaced.
|
||||
/// Adds a custom <see cref="TypeReader" /> to this <see cref="CommandService" /> for the supplied object
|
||||
/// type.
|
||||
/// If <typeparamref name="T" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> will
|
||||
/// also be added.
|
||||
/// If a default <see cref="TypeReader" /> exists for <typeparamref name="T" />, a warning will be logged
|
||||
/// and the default <see cref="TypeReader" /> will be replaced.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The object type to be read by the <see cref="TypeReader"/>.</typeparam>
|
||||
/// <param name="reader">An instance of the <see cref="TypeReader"/> to be added.</param>
|
||||
/// <param name="reader">An instance of the <see cref="TypeReader" /> to be added.</param>
|
||||
public void AddTypeReader<T>(TypeReader reader)
|
||||
=> AddTypeReader(typeof(T), reader);
|
||||
/// <summary>
|
||||
/// Adds a custom <see cref="TypeReader"/> to this <see cref="CommandService"/> for the supplied object type.
|
||||
/// If <paramref name="type"/> is a <see cref="ValueType"/>, a <see cref="NullableTypeReader{T}"/> for the value type will also be added.
|
||||
/// If a default <see cref="TypeReader"/> exists for <paramref name="type"/>, a warning will be logged and the default <see cref="TypeReader"/> will be replaced.
|
||||
/// Adds a custom <see cref="TypeReader" /> to this <see cref="CommandService" /> for the supplied object
|
||||
/// type.
|
||||
/// If <paramref name="type" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> for the
|
||||
/// value type will also be added.
|
||||
/// If a default <see cref="TypeReader" /> exists for <paramref name="type" />, a warning will be logged and
|
||||
/// the default <see cref="TypeReader" /> will be replaced.
|
||||
/// </summary>
|
||||
/// <param name="type">A <see cref="Type"/> instance for the type to be read.</param>
|
||||
/// <param name="reader">An instance of the <see cref="TypeReader"/> to be added.</param>
|
||||
/// <param name="type">A <see cref="Type" /> instance for the type to be read.</param>
|
||||
/// <param name="reader">An instance of the <see cref="TypeReader" /> to be added.</param>
|
||||
public void AddTypeReader(Type type, TypeReader reader)
|
||||
{
|
||||
if (_defaultTypeReaders.ContainsKey(type))
|
||||
@@ -245,21 +359,31 @@ namespace Discord.Commands
|
||||
AddTypeReader(type, reader, true);
|
||||
}
|
||||
/// <summary>
|
||||
/// Adds a custom <see cref="TypeReader"/> to this <see cref="CommandService"/> for the supplied object type.
|
||||
/// If <typeparamref name="T"/> is a <see cref="ValueType"/>, a <see cref="NullableTypeReader{T}"/> will also be added.
|
||||
/// Adds a custom <see cref="TypeReader" /> to this <see cref="CommandService" /> for the supplied object
|
||||
/// type.
|
||||
/// If <typeparamref name="T" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> will
|
||||
/// also be added.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The object type to be read by the <see cref="TypeReader"/>.</typeparam>
|
||||
/// <param name="reader">An instance of the <see cref="TypeReader"/> to be added.</param>
|
||||
/// <param name="replaceDefault">If <paramref name="reader"/> should replace the default <see cref="TypeReader"/> for <typeparamref name="T"/> if one exists.</param>
|
||||
/// <param name="reader">An instance of the <see cref="TypeReader" /> to be added.</param>
|
||||
/// <param name="replaceDefault">
|
||||
/// Defines whether the <see cref="TypeReader"/> should replace the default one for
|
||||
/// <see cref="Type" /> if it exists.
|
||||
/// </param>
|
||||
public void AddTypeReader<T>(TypeReader reader, bool replaceDefault)
|
||||
=> AddTypeReader(typeof(T), reader, replaceDefault);
|
||||
/// <summary>
|
||||
/// Adds a custom <see cref="TypeReader"/> to this <see cref="CommandService"/> for the supplied object type.
|
||||
/// If <paramref name="type"/> is a <see cref="ValueType"/>, a <see cref="NullableTypeReader{T}"/> for the value type will also be added.
|
||||
/// Adds a custom <see cref="TypeReader" /> to this <see cref="CommandService" /> for the supplied object
|
||||
/// type.
|
||||
/// If <paramref name="type" /> is a <see cref="ValueType" />, a nullable <see cref="TypeReader" /> for the
|
||||
/// value type will also be added.
|
||||
/// </summary>
|
||||
/// <param name="type">A <see cref="Type"/> instance for the type to be read.</param>
|
||||
/// <param name="reader">An instance of the <see cref="TypeReader"/> to be added.</param>
|
||||
/// <param name="replaceDefault">If <paramref name="reader"/> should replace the default <see cref="TypeReader"/> for <paramref name="type"/> if one exists.</param>
|
||||
/// <param name="type">A <see cref="Type" /> instance for the type to be read.</param>
|
||||
/// <param name="reader">An instance of the <see cref="TypeReader" /> to be added.</param>
|
||||
/// <param name="replaceDefault">
|
||||
/// Defines whether the <see cref="TypeReader"/> should replace the default one for <see cref="Type" /> if
|
||||
/// it exists.
|
||||
/// </param>
|
||||
public void AddTypeReader(Type type, TypeReader reader, bool replaceDefault)
|
||||
{
|
||||
if (replaceDefault && HasDefaultTypeReader(type))
|
||||
@@ -331,8 +455,20 @@ namespace Discord.Commands
|
||||
}
|
||||
|
||||
//Execution
|
||||
/// <summary>
|
||||
/// Searches for the command.
|
||||
/// </summary>
|
||||
/// <param name="context">The context of the command.</param>
|
||||
/// <param name="argPos">The position of which the command starts at.</param>
|
||||
/// <returns>The result containing the matching commands.</returns>
|
||||
public SearchResult Search(ICommandContext context, int argPos)
|
||||
=> Search(context.Message.Content.Substring(argPos));
|
||||
/// <summary>
|
||||
/// Searches for the command.
|
||||
/// </summary>
|
||||
/// <param name="context">The context of the command.</param>
|
||||
/// <param name="input">The command string.</param>
|
||||
/// <returns>The result containing the matching commands.</returns>
|
||||
public SearchResult Search(ICommandContext context, string input)
|
||||
=> Search(input);
|
||||
public SearchResult Search(string input)
|
||||
@@ -346,8 +482,30 @@ namespace Discord.Commands
|
||||
return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the command.
|
||||
/// </summary>
|
||||
/// <param name="context">The context of the command.</param>
|
||||
/// <param name="argPos">The position of which the command starts at.</param>
|
||||
/// <param name="services">The service to be used in the command's dependency injection.</param>
|
||||
/// <param name="multiMatchHandling">The handling mode when multiple command matches are found.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous execution operation. The task result contains the result of the
|
||||
/// command execution.
|
||||
/// </returns>
|
||||
public Task<IResult> ExecuteAsync(ICommandContext context, int argPos, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
|
||||
=> ExecuteAsync(context, context.Message.Content.Substring(argPos), services, multiMatchHandling);
|
||||
/// <summary>
|
||||
/// Executes the command.
|
||||
/// </summary>
|
||||
/// <param name="context">The context of the command.</param>
|
||||
/// <param name="input">The command string.</param>
|
||||
/// <param name="services">The service to be used in the command's dependency injection.</param>
|
||||
/// <param name="multiMatchHandling">The handling mode when multiple command matches are found.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous execution operation. The task result contains the result of the
|
||||
/// command execution.
|
||||
/// </returns>
|
||||
public async Task<IResult> ExecuteAsync(ICommandContext context, string input, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
|
||||
{
|
||||
services = services ?? EmptyServiceProvider.Instance;
|
||||
|
||||
@@ -1,29 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a configuration class for <see cref="CommandService"/>.
|
||||
/// </summary>
|
||||
public class CommandServiceConfig
|
||||
{
|
||||
/// <summary> Gets or sets the default RunMode commands should have, if one is not specified on the Command attribute or builder. </summary>
|
||||
/// <summary>
|
||||
/// Gets or sets the default <see cref="RunMode" /> commands should have, if one is not specified on the
|
||||
/// Command attribute or builder.
|
||||
/// </summary>
|
||||
public RunMode DefaultRunMode { get; set; } = RunMode.Sync;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="char"/> that separates an argument with another.
|
||||
/// </summary>
|
||||
public char SeparatorChar { get; set; } = ' ';
|
||||
|
||||
/// <summary> Determines whether commands should be case-sensitive. </summary>
|
||||
/// <summary>
|
||||
/// Gets or sets whether commands should be case-sensitive.
|
||||
/// </summary>
|
||||
public bool CaseSensitiveCommands { get; set; } = false;
|
||||
|
||||
/// <summary> Gets or sets the minimum log level severity that will be sent to the Log event. </summary>
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum log level severity that will be sent to the <see cref="CommandService.Log"/> event.
|
||||
/// </summary>
|
||||
public LogSeverity LogLevel { get; set; } = LogSeverity.Info;
|
||||
|
||||
/// <summary> Determines whether RunMode.Sync commands should push exceptions up to the caller. </summary>
|
||||
/// <summary>
|
||||
/// Gets or sets whether <see cref="RunMode.Sync"/> commands should push exceptions up to the caller.
|
||||
/// </summary>
|
||||
public bool ThrowOnError { get; set; } = true;
|
||||
|
||||
/// <summary> Collection of aliases that can wrap strings for command parsing.
|
||||
/// represents the opening quotation mark and the value is the corresponding closing mark.</summary>
|
||||
/// <summary>
|
||||
/// Collection of aliases for matching pairs of string delimiters.
|
||||
/// The dictionary stores the opening delimiter as a key, and the matching closing delimiter as the value.
|
||||
/// If no value is supplied <see cref="QuotationAliasUtils.GetDefaultAliasMap"/> will be used, which contains
|
||||
/// many regional equivalents.
|
||||
/// Only values that are specified in this map will be used as string delimiters, so if " is removed then
|
||||
/// it won't be used.
|
||||
/// If this map is set to null or empty, the default delimiter of " will be used.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code language="cs">
|
||||
/// QuotationMarkAliasMap = new Dictionary<char, char%gt;()
|
||||
/// {
|
||||
/// {'\"', '\"' },
|
||||
/// {'“', '”' },
|
||||
/// {'「', '」' },
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public Dictionary<char, char> QuotationMarkAliasMap { get; set; } = QuotationAliasUtils.GetDefaultAliasMap;
|
||||
|
||||
/// <summary> Determines whether extra parameters should be ignored. </summary>
|
||||
/// <summary>
|
||||
/// Gets or sets a value that indicates whether extra parameters should be ignored.
|
||||
/// </summary>
|
||||
public bool IgnoreExtraArgs { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,20 @@ using System;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="IUserMessage" /> that relates to commands.
|
||||
/// </summary>
|
||||
public static class MessageExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets whether the message starts with the provided character.
|
||||
/// </summary>
|
||||
/// <param name="msg">The message to check against.</param>
|
||||
/// <param name="c">The char prefix.</param>
|
||||
/// <param name="argPos">References where the command starts.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the message begins with the char <paramref name="c"/>; otherwise <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool HasCharPrefix(this IUserMessage msg, char c, ref int argPos)
|
||||
{
|
||||
var text = msg.Content;
|
||||
@@ -14,6 +26,9 @@ namespace Discord.Commands
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets whether the message starts with the provided string.
|
||||
/// </summary>
|
||||
public static bool HasStringPrefix(this IUserMessage msg, string str, ref int argPos, StringComparison comparisonType = StringComparison.Ordinal)
|
||||
{
|
||||
var text = msg.Content;
|
||||
@@ -24,6 +39,9 @@ namespace Discord.Commands
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets whether the message starts with the user's mention string.
|
||||
/// </summary>
|
||||
public static bool HasMentionPrefix(this IUserMessage msg, IUser user, ref int argPos)
|
||||
{
|
||||
var text = msg.Content;
|
||||
|
||||
@@ -8,10 +8,16 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the information of a command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This object contains the information of a command. This can include the module of the command, various
|
||||
/// descriptions regarding the command, and its <see cref="RunMode"/>.
|
||||
/// </remarks>
|
||||
[DebuggerDisplay("{Name,nq}")]
|
||||
public class CommandInfo
|
||||
{
|
||||
@@ -21,18 +27,63 @@ namespace Discord.Commands
|
||||
private readonly CommandService _commandService;
|
||||
private readonly Func<ICommandContext, object[], IServiceProvider, CommandInfo, Task> _action;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the module that the command belongs in.
|
||||
/// </summary>
|
||||
public ModuleInfo Module { get; }
|
||||
/// <summary>
|
||||
/// Gets the name of the command. If none is set, the first alias is used.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
/// <summary>
|
||||
/// Gets the summary of the command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This field returns the summary of the command. <see cref="Summary"/> and <see cref="Remarks"/> can be
|
||||
/// useful in help commands and various implementation that fetches details of the command for the user.
|
||||
/// </remarks>
|
||||
public string Summary { get; }
|
||||
/// <summary>
|
||||
/// Gets the remarks of the command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This field returns the summary of the command. <see cref="Summary"/> and <see cref="Remarks"/> can be
|
||||
/// useful in help commands and various implementation that fetches details of the command for the user.
|
||||
/// </remarks>
|
||||
public string Remarks { get; }
|
||||
/// <summary>
|
||||
/// Gets the priority of the command. This is used when there are multiple overloads of the command.
|
||||
/// </summary>
|
||||
public int Priority { get; }
|
||||
/// <summary>
|
||||
/// Indicates whether the command accepts a <see langword="params"/> <see cref="Type"/>[] for its
|
||||
/// parameter.
|
||||
/// </summary>
|
||||
public bool HasVarArgs { get; }
|
||||
/// <summary>
|
||||
/// Indicates whether extra arguments should be ignored for this command.
|
||||
/// </summary>
|
||||
public bool IgnoreExtraArgs { get; }
|
||||
/// <summary>
|
||||
/// Gets the <see cref="RunMode" /> that is being used for the command.
|
||||
/// </summary>
|
||||
public RunMode RunMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of aliases defined by the <see cref="AliasAttribute" /> of the command.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Aliases { get; }
|
||||
/// <summary>
|
||||
/// Gets a list of information about the parameters of the command.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ParameterInfo> Parameters { get; }
|
||||
/// <summary>
|
||||
/// Gets a list of preconditions defined by the <see cref="PreconditionAttribute" /> of the command.
|
||||
/// </summary>
|
||||
public IReadOnlyList<PreconditionAttribute> Preconditions { get; }
|
||||
/// <summary>
|
||||
/// Gets a list of attributes of the command.
|
||||
/// </summary>
|
||||
public IReadOnlyList<Attribute> Attributes { get; }
|
||||
|
||||
internal CommandInfo(CommandBuilder builder, ModuleInfo module, CommandService service)
|
||||
@@ -100,11 +151,11 @@ namespace Discord.Commands
|
||||
return PreconditionGroupResult.FromSuccess();
|
||||
}
|
||||
|
||||
var moduleResult = await CheckGroups(Module.Preconditions, "Module");
|
||||
var moduleResult = await CheckGroups(Module.Preconditions, "Module").ConfigureAwait(false);
|
||||
if (!moduleResult.IsSuccess)
|
||||
return moduleResult;
|
||||
|
||||
var commandResult = await CheckGroups(Preconditions, "Command");
|
||||
var commandResult = await CheckGroups(Preconditions, "Command").ConfigureAwait(false);
|
||||
if (!commandResult.IsSuccess)
|
||||
return commandResult;
|
||||
|
||||
@@ -124,7 +175,7 @@ namespace Discord.Commands
|
||||
|
||||
return await CommandParser.ParseArgsAsync(this, context, _commandService._ignoreExtraArgs, services, input, 0, _commandService._quotationMarkAliasMap).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
public Task<IResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services)
|
||||
{
|
||||
if (!parseResult.IsSuccess)
|
||||
@@ -248,11 +299,11 @@ namespace Discord.Commands
|
||||
foreach (object arg in argList)
|
||||
{
|
||||
if (i == argCount)
|
||||
throw new InvalidOperationException("Command was invoked with too many parameters");
|
||||
throw new InvalidOperationException("Command was invoked with too many parameters.");
|
||||
array[i++] = arg;
|
||||
}
|
||||
if (i < argCount)
|
||||
throw new InvalidOperationException("Command was invoked with too few parameters");
|
||||
throw new InvalidOperationException("Command was invoked with too few parameters.");
|
||||
|
||||
if (HasVarArgs)
|
||||
{
|
||||
|
||||
@@ -6,20 +6,59 @@ using Discord.Commands.Builders;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the information of a module.
|
||||
/// </summary>
|
||||
public class ModuleInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the command service associated with this module.
|
||||
/// </summary>
|
||||
public CommandService Service { get; }
|
||||
/// <summary>
|
||||
/// Gets the name of this module.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
/// <summary>
|
||||
/// Gets the summary of this module.
|
||||
/// </summary>
|
||||
public string Summary { get; }
|
||||
/// <summary>
|
||||
/// Gets the remarks of this module.
|
||||
/// </summary>
|
||||
public string Remarks { get; }
|
||||
/// <summary>
|
||||
/// Gets the group name (main prefix) of this module.
|
||||
/// </summary>
|
||||
public string Group { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only list of aliases associated with this module.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Aliases { get; }
|
||||
/// <summary>
|
||||
/// Gets a read-only list of commands associated with this module.
|
||||
/// </summary>
|
||||
public IReadOnlyList<CommandInfo> Commands { get; }
|
||||
/// <summary>
|
||||
/// Gets a read-only list of preconditions that apply to this module.
|
||||
/// </summary>
|
||||
public IReadOnlyList<PreconditionAttribute> Preconditions { get; }
|
||||
/// <summary>
|
||||
/// Gets a read-only list of attributes that apply to this module.
|
||||
/// </summary>
|
||||
public IReadOnlyList<Attribute> Attributes { get; }
|
||||
/// <summary>
|
||||
/// Gets a read-only list of submodules associated with this module.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ModuleInfo> Submodules { get; }
|
||||
/// <summary>
|
||||
/// Gets the parent module of this submodule if applicable.
|
||||
/// </summary>
|
||||
public ModuleInfo Parent { get; }
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether this module is a submodule or not.
|
||||
/// </summary>
|
||||
public bool IsSubmodule => Parent != null;
|
||||
|
||||
internal ModuleInfo(ModuleBuilder builder, CommandService service, IServiceProvider services, ModuleInfo parent = null)
|
||||
|
||||
@@ -2,25 +2,56 @@ using Discord.Commands.Builders;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the information of a parameter.
|
||||
/// </summary>
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class ParameterInfo
|
||||
{
|
||||
private readonly TypeReader _reader;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command that associates with this parameter.
|
||||
/// </summary>
|
||||
public CommandInfo Command { get; }
|
||||
/// <summary>
|
||||
/// Gets the name of this parameter.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
/// <summary>
|
||||
/// Gets the summary of this parameter.
|
||||
/// </summary>
|
||||
public string Summary { get; }
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether this parameter is optional or not.
|
||||
/// </summary>
|
||||
public bool IsOptional { get; }
|
||||
/// <summary>
|
||||
/// Gets a value that indicates whether this parameter is a remainder parameter or not.
|
||||
/// </summary>
|
||||
public bool IsRemainder { get; }
|
||||
public bool IsMultiple { get; }
|
||||
/// <summary>
|
||||
/// Gets the type of the parameter.
|
||||
/// </summary>
|
||||
public Type Type { get; }
|
||||
/// <summary>
|
||||
/// Gets the default value for this optional parameter if applicable.
|
||||
/// </summary>
|
||||
public object DefaultValue { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only list of precondition that apply to this parameter.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ParameterPreconditionAttribute> Preconditions { get; }
|
||||
/// <summary>
|
||||
/// Gets a read-only list of attributes that apply to this parameter.
|
||||
/// </summary>
|
||||
public IReadOnlyList<Attribute> Attributes { get; }
|
||||
|
||||
internal ParameterInfo(ParameterBuilder builder, CommandInfo command, CommandService service)
|
||||
@@ -65,4 +96,4 @@ namespace Discord.Commands
|
||||
public override string ToString() => Name;
|
||||
private string DebuggerDisplay => $"{Name}{(IsOptional ? " (Optional)" : "")}{(IsRemainder ? " (Remainder)" : "")}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Discord.Commands
|
||||
_commands = ImmutableArray.Create<CommandInfo>();
|
||||
}
|
||||
|
||||
/// <exception cref="InvalidOperationException">Cannot add commands to the root node.</exception>
|
||||
public void AddCommand(CommandService service, string text, int index, CommandInfo command)
|
||||
{
|
||||
int nextSegment = NextSegment(text, index, service._separatorChar);
|
||||
|
||||
@@ -4,32 +4,57 @@ using Discord.Commands.Builders;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base class for a command module to inherit from.
|
||||
/// </summary>
|
||||
public abstract class ModuleBase : ModuleBase<ICommandContext> { }
|
||||
|
||||
/// <summary>
|
||||
/// Provides a base class for a command module to inherit from.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">A class that implements <see cref="ICommandContext"/>.</typeparam>
|
||||
public abstract class ModuleBase<T> : IModuleBase
|
||||
where T : class, ICommandContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The underlying context of the command.
|
||||
/// </summary>
|
||||
/// <seealso cref="T:Discord.Commands.ICommandContext" />
|
||||
/// <seealso cref="T:Discord.Commands.CommandContext" />
|
||||
public T Context { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message to the source channel
|
||||
/// Sends a message to the source channel.
|
||||
/// </summary>
|
||||
/// <param name="message">Contents of the message; optional only if <paramref name="embed"/> is specified</param>
|
||||
/// <param name="isTTS">Specifies if Discord should read this message aloud using TTS</param>
|
||||
/// <param name="embed">An embed to be displayed alongside the message</param>
|
||||
/// <param name="message">
|
||||
/// Contents of the message; optional only if <paramref name="embed" /> is specified.
|
||||
/// </param>
|
||||
/// <param name="isTTS">Specifies if Discord should read this <paramref name="message"/> aloud using text-to-speech.</param>
|
||||
/// <param name="embed">An embed to be displayed alongside the <paramref name="message"/>.</param>
|
||||
protected virtual async Task<IUserMessage> ReplyAsync(string message = null, bool isTTS = false, Embed embed = null, RequestOptions options = null)
|
||||
{
|
||||
return await Context.Channel.SendMessageAsync(message, isTTS, embed, options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method to execute before executing the command.
|
||||
/// </summary>
|
||||
/// <param name="command">The <see cref="CommandInfo"/> of the command to be executed.</param>
|
||||
protected virtual void BeforeExecute(CommandInfo command)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method to execute after executing the command.
|
||||
/// </summary>
|
||||
/// <param name="command">The <see cref="CommandInfo"/> of the command to be executed.</param>
|
||||
protected virtual void AfterExecute(CommandInfo command)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method to execute when building the module.
|
||||
/// </summary>
|
||||
/// <param name="commandService">The <see cref="CommandService"/> used to create the module.</param>
|
||||
/// <param name="builder">The builder used to build the module.</param>
|
||||
protected virtual void OnModuleBuilding(CommandService commandService, ModuleBuilder builder)
|
||||
{
|
||||
}
|
||||
@@ -38,7 +63,7 @@ namespace Discord.Commands
|
||||
void IModuleBase.SetContext(ICommandContext context)
|
||||
{
|
||||
var newValue = context as T;
|
||||
Context = newValue ?? throw new InvalidOperationException($"Invalid context type. Expected {typeof(T).Name}, got {context.GetType().Name}");
|
||||
Context = newValue ?? throw new InvalidOperationException($"Invalid context type. Expected {typeof(T).Name}, got {context.GetType().Name}.");
|
||||
}
|
||||
void IModuleBase.BeforeExecute(CommandInfo command) => BeforeExecute(command);
|
||||
void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command);
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
namespace Discord.Commands
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the behavior when multiple matches are found during the command parsing stage.
|
||||
/// </summary>
|
||||
public enum MultiMatchHandling
|
||||
{
|
||||
/// <summary> Indicates that when multiple results are found, an exception should be thrown. </summary>
|
||||
Exception,
|
||||
/// <summary> Indicates that when multiple results are found, the best result should be chosen. </summary>
|
||||
Best
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,19 +6,29 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="TypeReader"/> for parsing objects implementing <see cref="IChannel"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This <see cref="TypeReader"/> is shipped with Discord.Net and is used by default to parse any
|
||||
/// <see cref="IChannel"/> implemented object within a command. The TypeReader will attempt to first parse the
|
||||
/// input by mention, then the snowflake identifier, then by name; the highest candidate will be chosen as the
|
||||
/// final output; otherwise, an erroneous <see cref="TypeReaderResult"/> is returned.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The type to be checked; must implement <see cref="IChannel"/>.</typeparam>
|
||||
public class ChannelTypeReader<T> : TypeReader
|
||||
where T : class, IChannel
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
|
||||
{
|
||||
if (context.Guild != null)
|
||||
{
|
||||
var results = new Dictionary<ulong, TypeReaderValue>();
|
||||
var channels = await context.Guild.GetChannelsAsync(CacheMode.CacheOnly).ConfigureAwait(false);
|
||||
ulong id;
|
||||
|
||||
//By Mention (1.0)
|
||||
if (MentionUtils.TryParseChannel(input, out id))
|
||||
if (MentionUtils.TryParseChannel(input, out ulong id))
|
||||
AddResult(results, await context.Guild.GetChannelAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f);
|
||||
|
||||
//By Id (0.9)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
@@ -44,6 +44,7 @@ namespace Discord.Commands
|
||||
_enumsByValue = byValueBuilder.ToImmutable();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
|
||||
{
|
||||
object enumValue;
|
||||
@@ -53,14 +54,14 @@ namespace Discord.Commands
|
||||
if (_enumsByValue.TryGetValue(baseValue, out enumValue))
|
||||
return Task.FromResult(TypeReaderResult.FromSuccess(enumValue));
|
||||
else
|
||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Value is not a {_enumType.Name}"));
|
||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Value is not a {_enumType.Name}."));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_enumsByName.TryGetValue(input.ToLower(), out enumValue))
|
||||
return Task.FromResult(TypeReaderResult.FromSuccess(enumValue));
|
||||
else
|
||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Value is not a {_enumType.Name}"));
|
||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Value is not a {_enumType.Name}."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,18 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="TypeReader"/> for parsing objects implementing <see cref="IMessage"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to be checked; must implement <see cref="IMessage"/>.</typeparam>
|
||||
public class MessageTypeReader<T> : TypeReader
|
||||
where T : class, IMessage
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
|
||||
{
|
||||
ulong id;
|
||||
|
||||
//By Id (1.0)
|
||||
if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id))
|
||||
if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out ulong id))
|
||||
{
|
||||
if (await context.Channel.GetMessageAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) is T msg)
|
||||
return TypeReaderResult.FromSuccess(msg);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
@@ -24,11 +24,12 @@ namespace Discord.Commands
|
||||
_baseTypeReader = baseTypeReader;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
|
||||
{
|
||||
if (string.Equals(input, "null", StringComparison.OrdinalIgnoreCase) || string.Equals(input, "nothing", StringComparison.OrdinalIgnoreCase))
|
||||
return TypeReaderResult.FromSuccess(new T?());
|
||||
return await _baseTypeReader.ReadAsync(context, input, services);
|
||||
return await _baseTypeReader.ReadAsync(context, input, services).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Commands
|
||||
@@ -17,14 +17,16 @@ namespace Discord.Commands
|
||||
private readonly TryParseDelegate<T> _tryParse;
|
||||
private readonly float _score;
|
||||
|
||||
/// <exception cref="ArgumentOutOfRangeException"><typeparamref name="T"/> must be within the range [0, 1].</exception>
|
||||
public PrimitiveTypeReader()
|
||||
: this(PrimitiveParsers.Get<T>(), 1)
|
||||
{ }
|
||||
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="score"/> must be within the range [0, 1].</exception>
|
||||
public PrimitiveTypeReader(TryParseDelegate<T> tryParse, float score)
|
||||
{
|
||||
if (score < 0 || score > 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(score), score, "Scores must be within the range [0, 1]");
|
||||
throw new ArgumentOutOfRangeException(nameof(score), score, "Scores must be within the range [0, 1].");
|
||||
|
||||
_tryParse = tryParse;
|
||||
_score = score;
|
||||
@@ -34,7 +36,7 @@ namespace Discord.Commands
|
||||
{
|
||||
if (_tryParse(input, out T value))
|
||||
return Task.FromResult(TypeReaderResult.FromSuccess(new TypeReaderValue(value, _score)));
|
||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Failed to parse {typeof(T).Name}"));
|
||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Failed to parse {typeof(T).Name}."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,20 +6,23 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="TypeReader"/> for parsing objects implementing <see cref="IRole"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to be checked; must implement <see cref="IRole"/>.</typeparam>
|
||||
public class RoleTypeReader<T> : TypeReader
|
||||
where T : class, IRole
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
|
||||
{
|
||||
ulong id;
|
||||
|
||||
if (context.Guild != null)
|
||||
{
|
||||
var results = new Dictionary<ulong, TypeReaderValue>();
|
||||
var roles = context.Guild.Roles;
|
||||
|
||||
//By Mention (1.0)
|
||||
if (MentionUtils.TryParseRole(input, out id))
|
||||
if (MentionUtils.TryParseRole(input, out var id))
|
||||
AddResult(results, context.Guild.GetRole(id) as T, 1.00f);
|
||||
|
||||
//By Id (0.9)
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Discord.Commands
|
||||
"%s's'", // 1s
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
|
||||
{
|
||||
return (TimeSpan.TryParseExact(input.ToLowerInvariant(), Formats, CultureInfo.InvariantCulture, out var timeSpan))
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a reader class that parses user input into a specified type.
|
||||
/// </summary>
|
||||
public abstract class TypeReader
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to parse the <paramref name="input"/> into the desired type.
|
||||
/// </summary>
|
||||
/// <param name="context">The context of the command.</param>
|
||||
/// <param name="input">The raw input of the command.</param>
|
||||
/// <param name="services">The service collection used for dependency injection.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous parsing operation. The task result contains the parsing result.
|
||||
/// </returns>
|
||||
public abstract Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,21 +7,25 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="TypeReader"/> for parsing objects implementing <see cref="IUser"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to be checked; must implement <see cref="IUser"/>.</typeparam>
|
||||
public class UserTypeReader<T> : TypeReader
|
||||
where T : class, IUser
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
|
||||
{
|
||||
var results = new Dictionary<ulong, TypeReaderValue>();
|
||||
IAsyncEnumerable<IUser> channelUsers = context.Channel.GetUsersAsync(CacheMode.CacheOnly).Flatten(); // it's better
|
||||
IReadOnlyCollection<IGuildUser> guildUsers = ImmutableArray.Create<IGuildUser>();
|
||||
ulong id;
|
||||
|
||||
if (context.Guild != null)
|
||||
guildUsers = await context.Guild.GetUsersAsync(CacheMode.CacheOnly).ConfigureAwait(false);
|
||||
|
||||
//By Mention (1.0)
|
||||
if (MentionUtils.TryParseUser(input, out id))
|
||||
if (MentionUtils.TryParseUser(input, out var id))
|
||||
{
|
||||
if (context.Guild != null)
|
||||
AddResult(results, await context.Guild.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f);
|
||||
@@ -46,7 +50,7 @@ namespace Discord.Commands
|
||||
if (ushort.TryParse(input.Substring(index + 1), out ushort discriminator))
|
||||
{
|
||||
var channelUser = await channelUsers.FirstOrDefault(x => x.DiscriminatorValue == discriminator &&
|
||||
string.Equals(username, x.Username, StringComparison.OrdinalIgnoreCase));
|
||||
string.Equals(username, x.Username, StringComparison.OrdinalIgnoreCase)).ConfigureAwait(false);
|
||||
AddResult(results, channelUser as T, channelUser?.Username == username ? 0.85f : 0.75f);
|
||||
|
||||
var guildUser = guildUsers.FirstOrDefault(x => x.DiscriminatorValue == discriminator &&
|
||||
@@ -59,7 +63,8 @@ namespace Discord.Commands
|
||||
{
|
||||
await channelUsers
|
||||
.Where(x => string.Equals(input, x.Username, StringComparison.OrdinalIgnoreCase))
|
||||
.ForEachAsync(channelUser => AddResult(results, channelUser as T, channelUser.Username == input ? 0.65f : 0.55f));
|
||||
.ForEachAsync(channelUser => AddResult(results, channelUser as T, channelUser.Username == input ? 0.65f : 0.55f))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
foreach (var guildUser in guildUsers.Where(x => string.Equals(input, x.Username, StringComparison.OrdinalIgnoreCase)))
|
||||
AddResult(results, guildUser as T, guildUser.Username == input ? 0.60f : 0.50f);
|
||||
@@ -69,7 +74,8 @@ namespace Discord.Commands
|
||||
{
|
||||
await channelUsers
|
||||
.Where(x => string.Equals(input, (x as IGuildUser)?.Nickname, StringComparison.OrdinalIgnoreCase))
|
||||
.ForEachAsync(channelUser => AddResult(results, channelUser as T, (channelUser as IGuildUser).Nickname == input ? 0.65f : 0.55f));
|
||||
.ForEachAsync(channelUser => AddResult(results, channelUser as T, (channelUser as IGuildUser).Nickname == input ? 0.65f : 0.55f))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
foreach (var guildUser in guildUsers.Where(x => string.Equals(input, x.Nickname, StringComparison.OrdinalIgnoreCase)))
|
||||
AddResult(results, guildUser as T, guildUser.Nickname == input ? 0.60f : 0.50f);
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information of the command's overall execution result.
|
||||
/// </summary>
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public struct ExecuteResult : IResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the exception that may have occurred during the command execution.
|
||||
/// </summary>
|
||||
public Exception Exception { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public CommandError? Error { get; }
|
||||
/// <inheritdoc />
|
||||
public string ErrorReason { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsSuccess => !Error.HasValue;
|
||||
|
||||
private ExecuteResult(Exception exception, CommandError? error, string errorReason)
|
||||
@@ -20,15 +29,56 @@ namespace Discord.Commands
|
||||
ErrorReason = errorReason;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="ExecuteResult" /> with no error, indicating a successful execution.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="ExecuteResult" /> that does not contain any errors.
|
||||
/// </returns>
|
||||
public static ExecuteResult FromSuccess()
|
||||
=> new ExecuteResult(null, null, null);
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="ExecuteResult" /> with a specified <see cref="CommandError" /> and its
|
||||
/// reason, indicating an unsuccessful execution.
|
||||
/// </summary>
|
||||
/// <param name="error">The type of error.</param>
|
||||
/// <param name="reason">The reason behind the error.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="ExecuteResult" /> that contains a <see cref="CommandError" /> and reason.
|
||||
/// </returns>
|
||||
public static ExecuteResult FromError(CommandError error, string reason)
|
||||
=> new ExecuteResult(null, error, reason);
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="ExecuteResult" /> with a specified exception, indicating an unsuccessful
|
||||
/// execution.
|
||||
/// </summary>
|
||||
/// <param name="ex">The exception that caused the command execution to fail.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="ExecuteResult" /> that contains the exception that caused the unsuccessful execution, along
|
||||
/// with a <see cref="CommandError" /> of type <c>Exception</c> as well as the exception message as the
|
||||
/// reason.
|
||||
/// </returns>
|
||||
public static ExecuteResult FromError(Exception ex)
|
||||
=> new ExecuteResult(ex, CommandError.Exception, ex.Message);
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="ExecuteResult" /> with a specified result; this may or may not be an
|
||||
/// successful execution depending on the <see cref="Discord.Commands.IResult.Error" /> and
|
||||
/// <see cref="Discord.Commands.IResult.ErrorReason" /> specified.
|
||||
/// </summary>
|
||||
/// <param name="result">The result to inherit from.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="ExecuteResult"/> that inherits the <see cref="IResult"/> error type and reason.
|
||||
/// </returns>
|
||||
public static ExecuteResult FromError(IResult result)
|
||||
=> new ExecuteResult(null, result.Error, result.ErrorReason);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string that indicates the execution result.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>Success</c> if <see cref="IsSuccess"/> is <c>true</c>; otherwise "<see cref="Error"/>:
|
||||
/// <see cref="ErrorReason"/>".
|
||||
/// </returns>
|
||||
public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}";
|
||||
private string DebuggerDisplay => IsSuccess ? "Success" : $"{Error}: {ErrorReason}";
|
||||
}
|
||||
|
||||
@@ -1,9 +1,31 @@
|
||||
namespace Discord.Commands
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information of the result related to a command.
|
||||
/// </summary>
|
||||
public interface IResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the error type that may have occurred during the operation.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="CommandError" /> indicating the type of error that may have occurred during the operation;
|
||||
/// <c>null</c> if the operation was successful.
|
||||
/// </returns>
|
||||
CommandError? Error { get; }
|
||||
/// <summary>
|
||||
/// Describes the reason for the error.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A string containing the error reason.
|
||||
/// </returns>
|
||||
string ErrorReason { get; }
|
||||
/// <summary>
|
||||
/// Indicates whether the operation was successful or not.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the result is positive; otherwise <c>false</c>.
|
||||
/// </returns>
|
||||
bool IsSuccess { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,21 @@ using System.Diagnostics;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information for the parsing result from the command service's parser.
|
||||
/// </summary>
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public struct ParseResult : IResult
|
||||
{
|
||||
public IReadOnlyList<TypeReaderResult> ArgValues { get; }
|
||||
public IReadOnlyList<TypeReaderResult> ParamValues { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CommandError? Error { get; }
|
||||
/// <inheritdoc/>
|
||||
public string ErrorReason { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsSuccess => !Error.HasValue;
|
||||
|
||||
private ParseResult(IReadOnlyList<TypeReaderResult> argValues, IReadOnlyList<TypeReaderResult> paramValues, CommandError? error, string errorReason)
|
||||
@@ -22,7 +28,7 @@ namespace Discord.Commands
|
||||
Error = error;
|
||||
ErrorReason = errorReason;
|
||||
}
|
||||
|
||||
|
||||
public static ParseResult FromSuccess(IReadOnlyList<TypeReaderResult> argValues, IReadOnlyList<TypeReaderResult> paramValues)
|
||||
{
|
||||
for (int i = 0; i < argValues.Count; i++)
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Discord.Commands
|
||||
PreconditionResults = (preconditions ?? new List<PreconditionResult>(0)).ToReadOnlyCollection();
|
||||
}
|
||||
|
||||
public static new PreconditionGroupResult FromSuccess()
|
||||
public new static PreconditionGroupResult FromSuccess()
|
||||
=> new PreconditionGroupResult(null, null, null);
|
||||
public static PreconditionGroupResult FromError(string reason, ICollection<PreconditionResult> preconditions)
|
||||
=> new PreconditionGroupResult(CommandError.UnmetPrecondition, reason, preconditions);
|
||||
|
||||
@@ -3,29 +3,56 @@ using System.Diagnostics;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a result type for command preconditions.
|
||||
/// </summary>
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public class PreconditionResult : IResult
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public CommandError? Error { get; }
|
||||
/// <inheritdoc/>
|
||||
public string ErrorReason { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsSuccess => !Error.HasValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="PreconditionResult" /> class with the command <paramref name="error"/> type
|
||||
/// and reason.
|
||||
/// </summary>
|
||||
/// <param name="error">The type of failure.</param>
|
||||
/// <param name="errorReason">The reason of failure.</param>
|
||||
protected PreconditionResult(CommandError? error, string errorReason)
|
||||
{
|
||||
Error = error;
|
||||
ErrorReason = errorReason;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="PreconditionResult" /> with no errors.
|
||||
/// </summary>
|
||||
public static PreconditionResult FromSuccess()
|
||||
=> new PreconditionResult(null, null);
|
||||
/// <summary>
|
||||
/// Returns a <see cref="PreconditionResult" /> with <see cref="CommandError.UnmetPrecondition" /> and the
|
||||
/// specified reason.
|
||||
/// </summary>
|
||||
/// <param name="reason">The reason of failure.</param>
|
||||
public static PreconditionResult FromError(string reason)
|
||||
=> new PreconditionResult(CommandError.UnmetPrecondition, reason);
|
||||
public static PreconditionResult FromError(Exception ex)
|
||||
=> new PreconditionResult(CommandError.Exception, ex.Message);
|
||||
/// <summary>
|
||||
/// Returns a <see cref="PreconditionResult" /> with the specified <paramref name="result"/> type.
|
||||
/// </summary>
|
||||
/// <param name="result">The result of failure.</param>
|
||||
public static PreconditionResult FromError(IResult result)
|
||||
=> new PreconditionResult(result.Error, result.ErrorReason);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string indicating whether the <see cref="PreconditionResult"/> is successful.
|
||||
/// </summary>
|
||||
public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}";
|
||||
private string DebuggerDisplay => IsSuccess ? "Success" : $"{Error}: {ErrorReason}";
|
||||
}
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
|
||||
public abstract class RuntimeResult : IResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="RuntimeResult" /> class with the type of error and reason.
|
||||
/// </summary>
|
||||
/// <param name="error">The type of failure, or <c>null</c> if none.</param>
|
||||
/// <param name="reason">The reason of failure.</param>
|
||||
protected RuntimeResult(CommandError? error, string reason)
|
||||
{
|
||||
Error = error;
|
||||
Reason = reason;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CommandError? Error { get; }
|
||||
/// <summary> Describes the execution reason or result. </summary>
|
||||
public string Reason { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsSuccess => !Error.HasValue;
|
||||
|
||||
/// <inheritdoc/>
|
||||
string IResult.ErrorReason => Reason;
|
||||
|
||||
public override string ToString() => Reason ?? (IsSuccess ? "Successful" : "Unsuccessful");
|
||||
|
||||
@@ -10,9 +10,12 @@ namespace Discord.Commands
|
||||
public string Text { get; }
|
||||
public IReadOnlyList<CommandMatch> Commands { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CommandError? Error { get; }
|
||||
/// <inheritdoc/>
|
||||
public string ErrorReason { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsSuccess => !Error.HasValue;
|
||||
|
||||
private SearchResult(string text, IReadOnlyList<CommandMatch> commands, CommandError? error, string errorReason)
|
||||
|
||||
@@ -27,10 +27,15 @@ namespace Discord.Commands
|
||||
{
|
||||
public IReadOnlyCollection<TypeReaderValue> Values { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CommandError? Error { get; }
|
||||
/// <inheritdoc/>
|
||||
public string ErrorReason { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsSuccess => !Error.HasValue;
|
||||
|
||||
/// <exception cref="InvalidOperationException">TypeReaderResult was not successful.</exception>
|
||||
public object BestMatch => IsSuccess
|
||||
? (Values.Count == 1 ? Values.Single().Value : Values.OrderByDescending(v => v.Score).First().Value)
|
||||
: throw new InvalidOperationException("TypeReaderResult was not successful.");
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
namespace Discord.Commands
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the behavior of the command execution workflow.
|
||||
/// </summary>
|
||||
/// <seealso cref="CommandServiceConfig"/>
|
||||
/// <seealso cref="CommandAttribute"/>
|
||||
public enum RunMode
|
||||
{
|
||||
/// <summary>
|
||||
/// The default behaviour set in <see cref="CommandServiceConfig"/>.
|
||||
/// </summary>
|
||||
Default,
|
||||
/// <summary>
|
||||
/// Executes the command on the same thread as gateway one.
|
||||
/// </summary>
|
||||
Sync,
|
||||
/// <summary>
|
||||
/// Executes the command on a different thread from the gateway one.
|
||||
/// </summary>
|
||||
Async
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility methods for generating matching pairs of unicode quotation marks for CommandServiceConfig
|
||||
/// Utility class which contains the default matching pairs of quotation marks for CommandServiceConfig
|
||||
/// </summary>
|
||||
internal static class QuotationAliasUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates an IEnumerable of characters representing open-close pairs of
|
||||
/// quotation punctuation.
|
||||
/// A default map of open-close pairs of quotation marks.
|
||||
/// Contains many regional and Unicode equivalents.
|
||||
/// Used in the <see cref="CommandServiceConfig"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="CommandServiceConfig.QuotationMarkAliasMap"/>
|
||||
internal static Dictionary<char, char> GetDefaultAliasMap
|
||||
{
|
||||
get
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Discord.Commands
|
||||
{
|
||||
@@ -38,7 +37,7 @@ namespace Discord.Commands
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Failed to create \"{ownerType.FullName}\"", ex);
|
||||
throw new Exception($"Failed to create \"{ownerType.FullName}\".", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,12 +45,12 @@ namespace Discord.Commands
|
||||
{
|
||||
var constructors = ownerType.DeclaredConstructors.Where(x => !x.IsStatic).ToArray();
|
||||
if (constructors.Length == 0)
|
||||
throw new InvalidOperationException($"No constructor found for \"{ownerType.FullName}\"");
|
||||
throw new InvalidOperationException($"No constructor found for \"{ownerType.FullName}\".");
|
||||
else if (constructors.Length > 1)
|
||||
throw new InvalidOperationException($"Multiple constructors found for \"{ownerType.FullName}\"");
|
||||
throw new InvalidOperationException($"Multiple constructors found for \"{ownerType.FullName}\".");
|
||||
return constructors[0];
|
||||
}
|
||||
private static System.Reflection.PropertyInfo[] GetProperties(TypeInfo ownerType)
|
||||
private static PropertyInfo[] GetProperties(TypeInfo ownerType)
|
||||
{
|
||||
var result = new List<System.Reflection.PropertyInfo>();
|
||||
while (ownerType != ObjectTypeInfo)
|
||||
@@ -71,7 +70,7 @@ namespace Discord.Commands
|
||||
return commands;
|
||||
if (memberType == typeof(IServiceProvider) || memberType == services.GetType())
|
||||
return services;
|
||||
var service = services?.GetService(memberType);
|
||||
var service = services.GetService(memberType);
|
||||
if (service != null)
|
||||
return service;
|
||||
throw new InvalidOperationException($"Failed to create \"{ownerType.FullName}\", dependency \"{memberType.Name}\" was not found.");
|
||||
|
||||
Reference in New Issue
Block a user