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.

Command.cs 6.6 kB

9 years ago
9 years ago
9 years ago
9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.Immutable;
  4. using System.Diagnostics;
  5. using System.Reflection;
  6. using System.Threading.Tasks;
  7. namespace Discord.Commands
  8. {
  9. [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
  10. public class Command
  11. {
  12. private readonly object _instance;
  13. private readonly Func<IMessage, IReadOnlyList<object>, Task> _action;
  14. public MethodInfo Source { get; }
  15. public Module Module { get; }
  16. public string Name { get; }
  17. public string Description { get; }
  18. public string Summary { get; }
  19. public string Text { get; }
  20. public IReadOnlyList<CommandParameter> Parameters { get; }
  21. public IReadOnlyList<PreconditionAttribute> Preconditions { get; }
  22. internal Command(MethodInfo source, Module module, object instance, CommandAttribute attribute, string groupPrefix)
  23. {
  24. Source = source;
  25. Module = module;
  26. _instance = instance;
  27. Name = source.Name;
  28. Text = groupPrefix + attribute.Text;
  29. var description = source.GetCustomAttribute<DescriptionAttribute>();
  30. if (description != null)
  31. Description = description.Text;
  32. var summary = source.GetCustomAttribute<SummaryAttribute>();
  33. if (summary != null)
  34. Summary = summary.Text;
  35. Parameters = BuildParameters(source);
  36. Preconditions = BuildPreconditions(source);
  37. _action = BuildAction(source);
  38. }
  39. public async Task<PreconditionResult> CheckPreconditions(IMessage context)
  40. {
  41. foreach (PreconditionAttribute precondition in Module.Preconditions)
  42. {
  43. var result = await precondition.CheckPermissions(context, this, Module.Instance).ConfigureAwait(false);
  44. if (!result.IsSuccess)
  45. return result;
  46. }
  47. foreach (PreconditionAttribute precondition in Preconditions)
  48. {
  49. var result = await precondition.CheckPermissions(context, this, Module.Instance).ConfigureAwait(false);
  50. if (!result.IsSuccess)
  51. return result;
  52. }
  53. return PreconditionResult.FromSuccess();
  54. }
  55. public async Task<ParseResult> Parse(IMessage msg, SearchResult searchResult, PreconditionResult? preconditionResult = null)
  56. {
  57. if (!searchResult.IsSuccess)
  58. return ParseResult.FromError(searchResult);
  59. if (preconditionResult != null && !preconditionResult.Value.IsSuccess)
  60. return ParseResult.FromError(preconditionResult.Value);
  61. return await CommandParser.ParseArgs(this, msg, searchResult.Text.Substring(Text.Length), 0).ConfigureAwait(false);
  62. }
  63. public async Task<ExecuteResult> Execute(IMessage msg, ParseResult parseResult)
  64. {
  65. if (!parseResult.IsSuccess)
  66. return ExecuteResult.FromError(parseResult);
  67. try
  68. {
  69. await _action.Invoke(msg, parseResult.Values);//Note: This code may need context
  70. return ExecuteResult.FromSuccess();
  71. }
  72. catch (Exception ex)
  73. {
  74. return ExecuteResult.FromError(ex);
  75. }
  76. }
  77. private IReadOnlyList<PreconditionAttribute> BuildPreconditions(MethodInfo methodInfo)
  78. {
  79. return methodInfo.GetCustomAttributes<PreconditionAttribute>().ToImmutableArray();
  80. }
  81. private IReadOnlyList<CommandParameter> BuildParameters(MethodInfo methodInfo)
  82. {
  83. var parameters = methodInfo.GetParameters();
  84. var paramBuilder = ImmutableArray.CreateBuilder<CommandParameter>(parameters.Length - 1);
  85. for (int i = 0; i < parameters.Length; i++)
  86. {
  87. var parameter = parameters[i];
  88. var type = parameter.ParameterType;
  89. if (i == 0)
  90. {
  91. if (type != typeof(IMessage))
  92. throw new InvalidOperationException("The first parameter of a command must be IMessage.");
  93. else
  94. continue;
  95. }
  96. //Detect 'params'
  97. bool isMultiple = parameter.GetCustomAttribute<ParamArrayAttribute>() != null;
  98. if (isMultiple)
  99. type = type.GetElementType();
  100. var reader = Module.Service.GetTypeReader(type);
  101. var typeInfo = type.GetTypeInfo();
  102. //Detect enums
  103. if (reader == null && typeInfo.IsEnum)
  104. {
  105. reader = EnumTypeReader.GetReader(type);
  106. Module.Service.AddTypeReader(type, reader);
  107. }
  108. if (reader == null)
  109. throw new InvalidOperationException($"{type.FullName} is not supported as a command parameter, are you missing a TypeReader?");
  110. bool isRemainder = parameter.GetCustomAttribute<RemainderAttribute>() != null;
  111. if (isRemainder && i != parameters.Length - 1)
  112. throw new InvalidOperationException("Remainder parameters must be the last parameter in a command.");
  113. string name = parameter.Name;
  114. string summary = parameter.GetCustomAttribute<DescriptionAttribute>()?.Text;
  115. bool isOptional = parameter.IsOptional;
  116. object defaultValue = parameter.HasDefaultValue ? parameter.DefaultValue : null;
  117. paramBuilder.Add(new CommandParameter(parameters[i], name, summary, type, reader, isOptional, isRemainder, isMultiple, defaultValue));
  118. }
  119. return paramBuilder.ToImmutable();
  120. }
  121. private Func<IMessage, IReadOnlyList<object>, Task> BuildAction(MethodInfo methodInfo)
  122. {
  123. if (methodInfo.ReturnType != typeof(Task))
  124. throw new InvalidOperationException("Commands must return a non-generic Task.");
  125. return (msg, args) =>
  126. {
  127. object[] newArgs = new object[args.Count + 1];
  128. newArgs[0] = msg;
  129. for (int i = 0; i < args.Count; i++)
  130. newArgs[i + 1] = args[i];
  131. var result = methodInfo.Invoke(_instance, newArgs);
  132. return result as Task ?? Task.CompletedTask;
  133. };
  134. }
  135. public override string ToString() => Name;
  136. private string DebuggerDisplay => $"{Module.Name}.{Name} ({Text})";
  137. }
  138. }