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.1 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. using System;
  2. using System.Linq;
  3. using System.Collections.Generic;
  4. using System.Collections.Immutable;
  5. using System.Collections.Concurrent;
  6. using System.Threading.Tasks;
  7. using System.Reflection;
  8. using Discord.Commands.Builders;
  9. using System.Diagnostics;
  10. namespace Discord.Commands
  11. {
  12. [DebuggerDisplay("{Name,nq}")]
  13. public class CommandInfo
  14. {
  15. private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList));
  16. private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>();
  17. private readonly Func<ICommandContext, object[], IDependencyMap, Task> _action;
  18. public ModuleInfo Module { get; }
  19. public string Name { get; }
  20. public string Summary { get; }
  21. public string Remarks { get; }
  22. public int Priority { get; }
  23. public bool HasVarArgs { get; }
  24. public RunMode RunMode { get; }
  25. public IReadOnlyList<string> Aliases { get; }
  26. public IReadOnlyList<ParameterInfo> Parameters { get; }
  27. public IReadOnlyList<PreconditionAttribute> Preconditions { get; }
  28. internal CommandInfo(CommandBuilder builder, ModuleInfo module, CommandService service)
  29. {
  30. Module = module;
  31. Name = builder.Name;
  32. Summary = builder.Summary;
  33. Remarks = builder.Remarks;
  34. RunMode = (builder.RunMode == RunMode.Default ? service._defaultRunMode : builder.RunMode);
  35. Priority = builder.Priority;
  36. Aliases = module.Aliases
  37. .Permutate(builder.RelativeAliases, (first, second) =>
  38. {
  39. if (first == "")
  40. return second;
  41. else if (second == "")
  42. return first;
  43. else
  44. return first + service._separatorChar + second;
  45. })
  46. .Concat(builder.AbsoluteAliases)
  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. ex = new CommandException(this, context, ex);
  145. await Module.Service._cmdLogger.ErrorAsync(ex).ConfigureAwait(false);
  146. if (Module.Service._throwOnError)
  147. throw;
  148. }
  149. await Module.Service._cmdLogger.VerboseAsync($"Executed {GetLogText(context)}").ConfigureAwait(false);
  150. }
  151. private object[] GenerateArgs(IEnumerable<object> argList, IEnumerable<object> paramsList)
  152. {
  153. int argCount = Parameters.Count;
  154. var array = new object[Parameters.Count];
  155. if (HasVarArgs)
  156. argCount--;
  157. int i = 0;
  158. foreach (var arg in argList)
  159. {
  160. if (i == argCount)
  161. throw new InvalidOperationException("Command was invoked with too many parameters");
  162. array[i++] = arg;
  163. }
  164. if (i < argCount)
  165. throw new InvalidOperationException("Command was invoked with too few parameters");
  166. if (HasVarArgs)
  167. {
  168. var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].Type, t =>
  169. {
  170. var method = _convertParamsMethod.MakeGenericMethod(t);
  171. return (Func<IEnumerable<object>, object>)method.CreateDelegate(typeof(Func<IEnumerable<object>, object>));
  172. });
  173. array[i] = func(paramsList);
  174. }
  175. return array;
  176. }
  177. private static T[] ConvertParamsList<T>(IEnumerable<object> paramsList)
  178. => paramsList.Cast<T>().ToArray();
  179. internal string GetLogText(ICommandContext context)
  180. {
  181. if (context.Guild != null)
  182. return $"\"{Name}\" for {context.User} in {context.Guild}/{context.Channel}";
  183. else
  184. return $"\"{Name}\" for {context.User} in {context.Channel}";
  185. }
  186. }
  187. }