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 9.4 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  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. [DebuggerDisplay("{Name,nq}")]
  14. public class CommandInfo
  15. {
  16. private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList));
  17. private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>();
  18. private readonly Func<ICommandContext, object[], IDependencyMap, Task> _action;
  19. public ModuleInfo Module { get; }
  20. public string Name { get; }
  21. public string Summary { get; }
  22. public string Remarks { get; }
  23. public int Priority { get; }
  24. public bool HasVarArgs { get; }
  25. public RunMode RunMode { get; }
  26. public IReadOnlyList<string> Aliases { get; }
  27. public IReadOnlyList<ParameterInfo> Parameters { get; }
  28. public IReadOnlyList<PreconditionAttribute> Preconditions { get; }
  29. internal CommandInfo(CommandBuilder builder, ModuleInfo module, CommandService service)
  30. {
  31. Module = module;
  32. Name = builder.Name;
  33. Summary = builder.Summary;
  34. Remarks = builder.Remarks;
  35. RunMode = (builder.RunMode == RunMode.Default ? service._defaultRunMode : builder.RunMode);
  36. Priority = builder.Priority;
  37. Aliases = module.Aliases
  38. .Permutate(builder.Aliases, (first, second) =>
  39. {
  40. if (first == "")
  41. return second;
  42. else if (second == "")
  43. return first;
  44. else
  45. return first + service._separatorChar + second;
  46. })
  47. .Select(x => service._caseSensitive ? x : x.ToLowerInvariant())
  48. .ToImmutableArray();
  49. Preconditions = builder.Preconditions.ToImmutableArray();
  50. Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray();
  51. HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].IsMultiple : false;
  52. _action = builder.Callback;
  53. }
  54. public async Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, IDependencyMap map = null)
  55. {
  56. if (map == null)
  57. map = DependencyMap.Empty;
  58. foreach (PreconditionAttribute precondition in Module.Preconditions)
  59. {
  60. var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false);
  61. if (!result.IsSuccess)
  62. return result;
  63. }
  64. foreach (PreconditionAttribute precondition in Preconditions)
  65. {
  66. var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false);
  67. if (!result.IsSuccess)
  68. return result;
  69. }
  70. return PreconditionResult.FromSuccess();
  71. }
  72. public async Task<ParseResult> ParseAsync(ICommandContext context, int startIndex, SearchResult searchResult, PreconditionResult? preconditionResult = null)
  73. {
  74. if (!searchResult.IsSuccess)
  75. return ParseResult.FromError(searchResult);
  76. if (preconditionResult != null && !preconditionResult.Value.IsSuccess)
  77. return ParseResult.FromError(preconditionResult.Value);
  78. string input = searchResult.Text.Substring(startIndex);
  79. return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false);
  80. }
  81. public Task<ExecuteResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IDependencyMap map)
  82. {
  83. if (!parseResult.IsSuccess)
  84. return Task.FromResult(ExecuteResult.FromError(parseResult));
  85. var argList = new object[parseResult.ArgValues.Count];
  86. for (int i = 0; i < parseResult.ArgValues.Count; i++)
  87. {
  88. if (!parseResult.ArgValues[i].IsSuccess)
  89. return Task.FromResult(ExecuteResult.FromError(parseResult.ArgValues[i]));
  90. argList[i] = parseResult.ArgValues[i].Values.First().Value;
  91. }
  92. var paramList = new object[parseResult.ParamValues.Count];
  93. for (int i = 0; i < parseResult.ParamValues.Count; i++)
  94. {
  95. if (!parseResult.ParamValues[i].IsSuccess)
  96. return Task.FromResult(ExecuteResult.FromError(parseResult.ParamValues[i]));
  97. paramList[i] = parseResult.ParamValues[i].Values.First().Value;
  98. }
  99. return ExecuteAsync(context, argList, paramList, map);
  100. }
  101. public async Task<ExecuteResult> ExecuteAsync(ICommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IDependencyMap map)
  102. {
  103. if (map == null)
  104. map = DependencyMap.Empty;
  105. try
  106. {
  107. object[] args = GenerateArgs(argList, paramList);
  108. for (int position = 0; position < Parameters.Count; position++)
  109. {
  110. var parameter = Parameters[position];
  111. var argument = args[position];
  112. var result = await parameter.CheckPreconditionsAsync(context, argument, map).ConfigureAwait(false);
  113. if (!result.IsSuccess)
  114. return ExecuteResult.FromError(result);
  115. }
  116. switch (RunMode)
  117. {
  118. case RunMode.Sync: //Always sync
  119. await ExecuteAsyncInternal(context, args, map).ConfigureAwait(false);
  120. break;
  121. case RunMode.Async: //Always async
  122. var t2 = Task.Run(async () =>
  123. {
  124. await ExecuteAsyncInternal(context, args, map).ConfigureAwait(false);
  125. });
  126. break;
  127. }
  128. return ExecuteResult.FromSuccess();
  129. }
  130. catch (Exception ex)
  131. {
  132. return ExecuteResult.FromError(ex);
  133. }
  134. }
  135. private async Task ExecuteAsyncInternal(ICommandContext context, object[] args, IDependencyMap map)
  136. {
  137. await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false);
  138. try
  139. {
  140. await _action(context, args, map).ConfigureAwait(false);
  141. }
  142. catch (Exception ex)
  143. {
  144. var originalEx = ex;
  145. while (ex is TargetInvocationException) //Happens with void-returning commands
  146. ex = ex.InnerException;
  147. var wrappedEx = new CommandException(this, context, ex);
  148. await Module.Service._cmdLogger.ErrorAsync(wrappedEx).ConfigureAwait(false);
  149. if (Module.Service._throwOnError)
  150. {
  151. if (ex == originalEx)
  152. throw;
  153. else
  154. ExceptionDispatchInfo.Capture(ex).Throw();
  155. }
  156. }
  157. await Module.Service._cmdLogger.VerboseAsync($"Executed {GetLogText(context)}").ConfigureAwait(false);
  158. }
  159. private object[] GenerateArgs(IEnumerable<object> argList, IEnumerable<object> paramsList)
  160. {
  161. int argCount = Parameters.Count;
  162. var array = new object[Parameters.Count];
  163. if (HasVarArgs)
  164. argCount--;
  165. int i = 0;
  166. foreach (var arg in argList)
  167. {
  168. if (i == argCount)
  169. throw new InvalidOperationException("Command was invoked with too many parameters");
  170. array[i++] = arg;
  171. }
  172. if (i < argCount)
  173. throw new InvalidOperationException("Command was invoked with too few parameters");
  174. if (HasVarArgs)
  175. {
  176. var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].Type, t =>
  177. {
  178. var method = _convertParamsMethod.MakeGenericMethod(t);
  179. return (Func<IEnumerable<object>, object>)method.CreateDelegate(typeof(Func<IEnumerable<object>, object>));
  180. });
  181. array[i] = func(paramsList);
  182. }
  183. return array;
  184. }
  185. private static T[] ConvertParamsList<T>(IEnumerable<object> paramsList)
  186. => paramsList.Cast<T>().ToArray();
  187. internal string GetLogText(ICommandContext context)
  188. {
  189. if (context.Guild != null)
  190. return $"\"{Name}\" for {context.User} in {context.Guild}/{context.Channel}";
  191. else
  192. return $"\"{Name}\" for {context.User} in {context.Channel}";
  193. }
  194. }
  195. }