You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

CommandInfo.cs 15 kB

8 years ago
8 years ago
7 years ago
7 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. using Discord.Commands.Builders;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Collections.Immutable;
  5. using System.Collections.Concurrent;
  6. using System.Diagnostics;
  7. using System.Linq;
  8. using System.Reflection;
  9. using System.Runtime.ExceptionServices;
  10. using System.Threading.Tasks;
  11. namespace Discord.Commands
  12. {
  13. /// <summary>
  14. /// Provides the information of a command.
  15. /// </summary>
  16. /// <remarks>
  17. /// This object contains the information of a command. This can include the module of the command, various
  18. /// descriptions regarding the command, and its <see cref="RunMode"/>.
  19. /// </remarks>
  20. [DebuggerDisplay("{Name,nq}")]
  21. public class CommandInfo
  22. {
  23. private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList));
  24. private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>();
  25. private readonly CommandService _commandService;
  26. private readonly Func<ICommandContext, object[], IServiceProvider, CommandInfo, Task> _action;
  27. /// <summary>
  28. /// Gets the module that the command belongs in.
  29. /// </summary>
  30. public ModuleInfo Module { get; }
  31. /// <summary>
  32. /// Gets the name of the command. If none is set, the first alias is used.
  33. /// </summary>
  34. public string Name { get; }
  35. /// <summary>
  36. /// Gets the summary of the command.
  37. /// </summary>
  38. /// <remarks>
  39. /// This field returns the summary of the command. <see cref="Summary"/> and <see cref="Remarks"/> can be
  40. /// useful in help commands and various implementation that fetches details of the command for the user.
  41. /// </remarks>
  42. public string Summary { get; }
  43. /// <summary>
  44. /// Gets the remarks of the command.
  45. /// </summary>
  46. /// <remarks>
  47. /// This field returns the summary of the command. <see cref="Summary"/> and <see cref="Remarks"/> can be
  48. /// useful in help commands and various implementation that fetches details of the command for the user.
  49. /// </remarks>
  50. public string Remarks { get; }
  51. /// <summary>
  52. /// Gets the priority of the command. This is used when there are multiple overloads of the command.
  53. /// </summary>
  54. public int Priority { get; }
  55. /// <summary>
  56. /// Indicates whether the command accepts a <see langword="params"/> <see cref="Type"/>[] for its
  57. /// parameter.
  58. /// </summary>
  59. public bool HasVarArgs { get; }
  60. /// <summary>
  61. /// Indicates whether extra arguments should be ignored for this command.
  62. /// </summary>
  63. public bool IgnoreExtraArgs { get; }
  64. /// <summary>
  65. /// Gets the <see cref="RunMode" /> that is being used for the command.
  66. /// </summary>
  67. public RunMode RunMode { get; }
  68. /// <summary>
  69. /// Gets a list of aliases defined by the <see cref="AliasAttribute" /> of the command.
  70. /// </summary>
  71. public IReadOnlyList<string> Aliases { get; }
  72. /// <summary>
  73. /// Gets a list of information about the parameters of the command.
  74. /// </summary>
  75. public IReadOnlyList<ParameterInfo> Parameters { get; }
  76. /// <summary>
  77. /// Gets a list of preconditions defined by the <see cref="PreconditionAttribute" /> of the command.
  78. /// </summary>
  79. public IReadOnlyList<PreconditionAttribute> Preconditions { get; }
  80. /// <summary>
  81. /// Gets a list of attributes of the command.
  82. /// </summary>
  83. public IReadOnlyList<Attribute> Attributes { get; }
  84. internal CommandInfo(CommandBuilder builder, ModuleInfo module, CommandService service)
  85. {
  86. Module = module;
  87. Name = builder.Name;
  88. Summary = builder.Summary;
  89. Remarks = builder.Remarks;
  90. RunMode = (builder.RunMode == RunMode.Default ? service._defaultRunMode : builder.RunMode);
  91. Priority = builder.Priority;
  92. Aliases = module.Aliases
  93. .Permutate(builder.Aliases, (first, second) =>
  94. {
  95. if (first == "")
  96. return second;
  97. else if (second == "")
  98. return first;
  99. else
  100. return first + service._separatorChar + second;
  101. })
  102. .Select(x => service._caseSensitive ? x : x.ToLowerInvariant())
  103. .ToImmutableArray();
  104. Preconditions = builder.Preconditions.ToImmutableArray();
  105. Attributes = builder.Attributes.ToImmutableArray();
  106. Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray();
  107. HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].IsMultiple : false;
  108. IgnoreExtraArgs = builder.IgnoreExtraArgs;
  109. _action = builder.Callback;
  110. _commandService = service;
  111. }
  112. public async Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null)
  113. {
  114. services = services ?? EmptyServiceProvider.Instance;
  115. async Task<PreconditionResult> CheckGroups(IEnumerable<PreconditionAttribute> preconditions, string type)
  116. {
  117. foreach (IGrouping<string, PreconditionAttribute> preconditionGroup in preconditions.GroupBy(p => p.Group, StringComparer.Ordinal))
  118. {
  119. if (preconditionGroup.Key == null)
  120. {
  121. foreach (PreconditionAttribute precondition in preconditionGroup)
  122. {
  123. var result = await precondition.CheckPermissionsAsync(context, this, services).ConfigureAwait(false);
  124. if (!result.IsSuccess)
  125. return result;
  126. }
  127. }
  128. else
  129. {
  130. var results = new List<PreconditionResult>();
  131. foreach (PreconditionAttribute precondition in preconditionGroup)
  132. results.Add(await precondition.CheckPermissionsAsync(context, this, services).ConfigureAwait(false));
  133. if (!results.Any(p => p.IsSuccess))
  134. return PreconditionGroupResult.FromError($"{type} precondition group {preconditionGroup.Key} failed.", results);
  135. }
  136. }
  137. return PreconditionGroupResult.FromSuccess();
  138. }
  139. var moduleResult = await CheckGroups(Module.Preconditions, "Module").ConfigureAwait(false);
  140. if (!moduleResult.IsSuccess)
  141. return moduleResult;
  142. var commandResult = await CheckGroups(Preconditions, "Command").ConfigureAwait(false);
  143. if (!commandResult.IsSuccess)
  144. return commandResult;
  145. return PreconditionResult.FromSuccess();
  146. }
  147. public async Task<ParseResult> ParseAsync(ICommandContext context, int startIndex, SearchResult searchResult, PreconditionResult preconditionResult = null, IServiceProvider services = null)
  148. {
  149. services = services ?? EmptyServiceProvider.Instance;
  150. if (!searchResult.IsSuccess)
  151. return ParseResult.FromError(searchResult);
  152. if (preconditionResult != null && !preconditionResult.IsSuccess)
  153. return ParseResult.FromError(preconditionResult);
  154. string input = searchResult.Text.Substring(startIndex);
  155. return await CommandParser.ParseArgsAsync(this, context, _commandService._ignoreExtraArgs, services, input, 0, _commandService._quotationMarkAliasMap).ConfigureAwait(false);
  156. }
  157. public Task<IResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services)
  158. {
  159. if (!parseResult.IsSuccess)
  160. return Task.FromResult((IResult)ExecuteResult.FromError(parseResult));
  161. var argList = new object[parseResult.ArgValues.Count];
  162. for (int i = 0; i < parseResult.ArgValues.Count; i++)
  163. {
  164. if (!parseResult.ArgValues[i].IsSuccess)
  165. return Task.FromResult((IResult)ExecuteResult.FromError(parseResult.ArgValues[i]));
  166. argList[i] = parseResult.ArgValues[i].Values.First().Value;
  167. }
  168. var paramList = new object[parseResult.ParamValues.Count];
  169. for (int i = 0; i < parseResult.ParamValues.Count; i++)
  170. {
  171. if (!parseResult.ParamValues[i].IsSuccess)
  172. return Task.FromResult((IResult)ExecuteResult.FromError(parseResult.ParamValues[i]));
  173. paramList[i] = parseResult.ParamValues[i].Values.First().Value;
  174. }
  175. return ExecuteAsync(context, argList, paramList, services);
  176. }
  177. public async Task<IResult> ExecuteAsync(ICommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IServiceProvider services)
  178. {
  179. services = services ?? EmptyServiceProvider.Instance;
  180. try
  181. {
  182. object[] args = GenerateArgs(argList, paramList);
  183. for (int position = 0; position < Parameters.Count; position++)
  184. {
  185. var parameter = Parameters[position];
  186. object argument = args[position];
  187. var result = await parameter.CheckPreconditionsAsync(context, argument, services).ConfigureAwait(false);
  188. if (!result.IsSuccess)
  189. return ExecuteResult.FromError(result);
  190. }
  191. switch (RunMode)
  192. {
  193. case RunMode.Sync: //Always sync
  194. return await ExecuteInternalAsync(context, args, services).ConfigureAwait(false);
  195. case RunMode.Async: //Always async
  196. var t2 = Task.Run(async () =>
  197. {
  198. await ExecuteInternalAsync(context, args, services).ConfigureAwait(false);
  199. });
  200. break;
  201. }
  202. return ExecuteResult.FromSuccess();
  203. }
  204. catch (Exception ex)
  205. {
  206. return ExecuteResult.FromError(ex);
  207. }
  208. }
  209. private async Task<IResult> ExecuteInternalAsync(ICommandContext context, object[] args, IServiceProvider services)
  210. {
  211. await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false);
  212. try
  213. {
  214. var task = _action(context, args, services, this);
  215. if (task is Task<IResult> resultTask)
  216. {
  217. var result = await resultTask.ConfigureAwait(false);
  218. await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false);
  219. if (result is RuntimeResult execResult)
  220. return execResult;
  221. }
  222. else if (task is Task<ExecuteResult> execTask)
  223. {
  224. var result = await execTask.ConfigureAwait(false);
  225. await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false);
  226. return result;
  227. }
  228. else
  229. {
  230. await task.ConfigureAwait(false);
  231. var result = ExecuteResult.FromSuccess();
  232. await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false);
  233. }
  234. var executeResult = ExecuteResult.FromSuccess();
  235. return executeResult;
  236. }
  237. catch (Exception ex)
  238. {
  239. var originalEx = ex;
  240. while (ex is TargetInvocationException) //Happens with void-returning commands
  241. ex = ex.InnerException;
  242. var wrappedEx = new CommandException(this, context, ex);
  243. await Module.Service._cmdLogger.ErrorAsync(wrappedEx).ConfigureAwait(false);
  244. if (Module.Service._throwOnError)
  245. {
  246. if (ex == originalEx)
  247. throw;
  248. else
  249. ExceptionDispatchInfo.Capture(ex).Throw();
  250. }
  251. return ExecuteResult.FromError(CommandError.Exception, ex.Message);
  252. }
  253. finally
  254. {
  255. await Module.Service._cmdLogger.VerboseAsync($"Executed {GetLogText(context)}").ConfigureAwait(false);
  256. }
  257. }
  258. private object[] GenerateArgs(IEnumerable<object> argList, IEnumerable<object> paramsList)
  259. {
  260. int argCount = Parameters.Count;
  261. var array = new object[Parameters.Count];
  262. if (HasVarArgs)
  263. argCount--;
  264. int i = 0;
  265. foreach (object arg in argList)
  266. {
  267. if (i == argCount)
  268. throw new InvalidOperationException("Command was invoked with too many parameters.");
  269. array[i++] = arg;
  270. }
  271. if (i < argCount)
  272. throw new InvalidOperationException("Command was invoked with too few parameters.");
  273. if (HasVarArgs)
  274. {
  275. var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].Type, t =>
  276. {
  277. var method = _convertParamsMethod.MakeGenericMethod(t);
  278. return (Func<IEnumerable<object>, object>)method.CreateDelegate(typeof(Func<IEnumerable<object>, object>));
  279. });
  280. array[i] = func(paramsList);
  281. }
  282. return array;
  283. }
  284. private static T[] ConvertParamsList<T>(IEnumerable<object> paramsList)
  285. => paramsList.Cast<T>().ToArray();
  286. internal string GetLogText(ICommandContext context)
  287. {
  288. if (context.Guild != null)
  289. return $"\"{Name}\" for {context.User} in {context.Guild}/{context.Channel}";
  290. else
  291. return $"\"{Name}\" for {context.User} in {context.Channel}";
  292. }
  293. }
  294. }