Skip to content

Commit 244818e

Browse files
committed
Update Remora.Commands.
1 parent ab0811b commit 244818e

File tree

6 files changed

+41
-59
lines changed

6 files changed

+41
-59
lines changed

Directory.Packages.props

+7-7
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@
2121
<PackageVersion Include="OneOf" Version="3.0.271" />
2222
<PackageVersion Include="Polly" Version="8.5.2" />
2323
<PackageVersion Include="Polly.Contrib.WaitAndRetry" Version="1.1.1" />
24-
<PackageVersion Include="Remora.Commands" Version="10.0.6" />
25-
<PackageVersion Include="Remora.Extensions.Options.Immutable" Version="1.0.8" />
26-
<PackageVersion Include="Remora.Rest" Version="3.4.0" />
27-
<PackageVersion Include="Remora.Rest.Core" Version="2.2.1" />
28-
<PackageVersion Include="Remora.Rest.Xunit" Version="3.0.4" />
29-
<PackageVersion Include="Remora.Results" Version="7.4.1" />
30-
<PackageVersion Include="Remora.Results.Analyzers" Version="1.0.2" PrivateAssets="all" />
24+
<PackageVersion Include="Remora.Commands" Version="11.0.1" />
25+
<PackageVersion Include="Remora.Extensions.Options.Immutable" Version="2.0.0" />
26+
<PackageVersion Include="Remora.Rest" Version="4.0.0" />
27+
<PackageVersion Include="Remora.Rest.Core" Version="3.0.0" />
28+
<PackageVersion Include="Remora.Rest.Xunit" Version="4.0.0" />
29+
<PackageVersion Include="Remora.Results" Version="8.0.0" />
30+
<PackageVersion Include="Remora.Results.Analyzers" Version="1.0.3" PrivateAssets="all" />
3131
<PackageVersion Include="RichardSzalay.MockHttp" Version="7.0.0" />
3232
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
3333
<PackageVersion Include="System.Text.Json" Version="9.0.2" />

Remora.Discord.Commands/Extensions/CommandNodeExtensions.cs

+5-10
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
//
2222

2323
using System;
24+
using System.Linq;
2425
using System.Reflection;
2526
using JetBrains.Annotations;
2627
using Remora.Commands.Trees.Nodes;
@@ -42,7 +43,7 @@ public static class CommandNodeExtensions
4243
/// <returns>The command type.</returns>
4344
public static ApplicationCommandType GetCommandType(this CommandNode node)
4445
{
45-
var attribute = node.CommandMethod.GetCustomAttribute<CommandTypeAttribute>();
46+
var attribute = node.Attributes.OfType<CommandTypeAttribute>().SingleOrDefault();
4647
return attribute?.Type ?? ApplicationCommandType.ChatInput;
4748
}
4849

@@ -63,21 +64,15 @@ public static T? FindCustomAttributeOnLocalTree<T>
6364
bool includeAncestors = true
6465
) where T : Attribute
6566
{
66-
// Attempt to first find the attribute on the command itself
67-
var attribute = node.CommandMethod.GetCustomAttribute<T>();
67+
// Attempt to first find the attribute on the command itself. This catches attributes from any unnamed groups,
68+
// which get copied onto the command.
69+
var attribute = node.Attributes.OfType<T>().FirstOrDefault();
6870

6971
if (attribute is not null || !includeAncestors)
7072
{
7173
return attribute;
7274
}
7375

74-
// Check the associated group type directly first
75-
attribute = node.GroupType.GetCustomAttribute<T>();
76-
if (attribute is not null)
77-
{
78-
return attribute;
79-
}
80-
8176
// Traverse each parent group node, until we find the root node
8277
var parent = node.Parent;
8378
while (parent is GroupNode group)

Remora.Discord.Commands/Extensions/CommandTreeExtensions.cs

+19-27
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,7 @@ private static TopLevelMetadata GetCommandNodeMetadata(CommandNode commandNode)
254254

255255
IDiscordPermissionSet? defaultMemberPermissions = null;
256256
var memberPermissionsAttribute =
257-
commandNode.GroupType.GetCustomAttribute<DiscordDefaultMemberPermissionsAttribute>() ??
258-
commandNode.CommandMethod.GetCustomAttribute<DiscordDefaultMemberPermissionsAttribute>();
257+
commandNode.FindCustomAttributeOnLocalTree<DiscordDefaultMemberPermissionsAttribute>();
259258

260259
if (memberPermissionsAttribute is not null)
261260
{
@@ -267,38 +266,31 @@ private static TopLevelMetadata GetCommandNodeMetadata(CommandNode commandNode)
267266

268267
var directMessagePermission = default(Optional<bool>);
269268
var directMessagePermissionAttribute =
270-
commandNode.GroupType.GetCustomAttribute<DiscordDefaultDMPermissionAttribute>() ??
271-
commandNode.CommandMethod.GetCustomAttribute<DiscordDefaultDMPermissionAttribute>();
269+
commandNode.FindCustomAttributeOnLocalTree<DiscordDefaultDMPermissionAttribute>();
272270

273271
if (directMessagePermissionAttribute is not null)
274272
{
275273
directMessagePermission = directMessagePermissionAttribute.IsExecutableInDMs;
276274
}
277275

278276
var isNsfw = default(Optional<bool>);
279-
var nsfwAttribute =
280-
commandNode.GroupType.GetCustomAttribute<DiscordNsfwAttribute>() ??
281-
commandNode.CommandMethod.GetCustomAttribute<DiscordNsfwAttribute>();
277+
var nsfwAttribute = commandNode.FindCustomAttributeOnLocalTree<DiscordNsfwAttribute>();
282278

283279
if (nsfwAttribute is not null)
284280
{
285281
isNsfw = nsfwAttribute.IsNsfw;
286282
}
287283

288284
var allowedContextTypes = default(Optional<IReadOnlyList<InteractionContextType>>);
289-
var contextsAttribute =
290-
commandNode.GroupType.GetCustomAttribute<AllowedContextsAttribute>() ??
291-
commandNode.CommandMethod.GetCustomAttribute<AllowedContextsAttribute>();
285+
var contextsAttribute = commandNode.FindCustomAttributeOnLocalTree<AllowedContextsAttribute>();
292286

293287
if (contextsAttribute is not null)
294288
{
295289
allowedContextTypes = contextsAttribute.Contexts.AsOptional();
296290
}
297291

298292
var allowedIntegrationTypes = default(Optional<IReadOnlyList<ApplicationIntegrationType>>);
299-
var integrationAttribute =
300-
commandNode.GroupType.GetCustomAttribute<DiscordInstallContextAttribute>() ??
301-
commandNode.CommandMethod.GetCustomAttribute<DiscordInstallContextAttribute>();
293+
var integrationAttribute = commandNode.FindCustomAttributeOnLocalTree<DiscordInstallContextAttribute>();
302294

303295
if (integrationAttribute is not null)
304296
{
@@ -598,7 +590,7 @@ ILocalizationProvider localizationProvider
598590

599591
if (treeDepth > 1)
600592
{
601-
if (command.CommandMethod.GetCustomAttribute<DiscordDefaultDMPermissionAttribute>() is not null)
593+
if (command.Attributes.OfType<DiscordDefaultDMPermissionAttribute>().FirstOrDefault() is not null)
602594
{
603595
throw new InvalidNodeException
604596
(
@@ -607,7 +599,7 @@ ILocalizationProvider localizationProvider
607599
);
608600
}
609601

610-
if (command.CommandMethod.GetCustomAttribute<DiscordDefaultMemberPermissionsAttribute>() is not null)
602+
if (command.Attributes.OfType<DiscordDefaultMemberPermissionsAttribute>().FirstOrDefault() is not null)
611603
{
612604
throw new InvalidNodeException
613605
(
@@ -616,7 +608,7 @@ ILocalizationProvider localizationProvider
616608
);
617609
}
618610

619-
if (command.CommandMethod.GetCustomAttribute<DiscordNsfwAttribute>() is not null)
611+
if (command.Attributes.OfType<DiscordNsfwAttribute>().FirstOrDefault() is not null)
620612
{
621613
throw new InvalidNodeException
622614
(
@@ -638,9 +630,9 @@ ILocalizationProvider localizationProvider
638630
);
639631
}
640632

641-
var parameters = command.CommandMethod.GetParameters();
633+
var parameters = command.Shape.Parameters;
642634
var expectedParameter = commandType.AsParameterName();
643-
if (parameters.Length != 1 || parameters[0].Name != expectedParameter)
635+
if (parameters.Count != 1 || parameters[0].HintName != expectedParameter)
644636
{
645637
throw new InvalidNodeException
646638
(
@@ -718,7 +710,7 @@ ILocalizationProvider localizationProvider
718710
var actualParameterType = parameter.GetActualParameterType();
719711
var (enableAutocomplete, choices) = GetParameterChoices
720712
(
721-
parameter.Parameter,
713+
parameter,
722714
actualParameterType,
723715
localizationProvider
724716
);
@@ -757,7 +749,7 @@ ILocalizationProvider localizationProvider
757749
}
758750

759751
// Collection parameters
760-
var rangeAttribute = parameter.Parameter.GetCustomAttribute<RangeAttribute>();
752+
var rangeAttribute = parameter.Attributes.OfType<RangeAttribute>().SingleOrDefault();
761753
var (minElements, maxElements) = (rangeAttribute?.GetMin() ?? 1, rangeAttribute?.GetMax());
762754

763755
for (ulong i = 0; i < (maxElements ?? minElements); i++)
@@ -801,7 +793,7 @@ ILocalizationProvider localizationProvider
801793
private static (Optional<bool> EnableAutocomplete, Optional<IReadOnlyList<IApplicationCommandOptionChoice>> Choices)
802794
GetParameterChoices
803795
(
804-
ParameterInfo parameter,
796+
IParameterShape parameter,
805797
Type actualParameterType,
806798
ILocalizationProvider localizationProvider
807799
)
@@ -824,7 +816,7 @@ ILocalizationProvider localizationProvider
824816
}
825817
else
826818
{
827-
if (parameter.GetCustomAttribute<AutocompleteAttribute>() is not null)
819+
if (parameter.Attributes.OfType<AutocompleteAttribute>().SingleOrDefault() is not null)
828820
{
829821
enableAutocomplete = true;
830822
}
@@ -840,7 +832,7 @@ private static Optional<IReadOnlyList<ChannelType>> CreateChannelTypesOption
840832
ApplicationCommandOptionType parameterType
841833
)
842834
{
843-
var channelTypesAttribute = parameter.Parameter.GetCustomAttribute<ChannelTypesAttribute>();
835+
var channelTypesAttribute = parameter.Attributes.OfType<ChannelTypesAttribute>().SingleOrDefault();
844836
if (channelTypesAttribute is not null && parameterType is not ApplicationCommandOptionType.Channel)
845837
{
846838
throw new InvalidCommandParameterException
@@ -1063,8 +1055,8 @@ private static (MinValueAttribute? MinValue, MaxValueAttribute? MaxValue) GetNum
10631055
ApplicationCommandOptionType discordType
10641056
)
10651057
{
1066-
var minValue = parameter.Parameter.GetCustomAttribute<MinValueAttribute>();
1067-
var maxValue = parameter.Parameter.GetCustomAttribute<MaxValueAttribute>();
1058+
var minValue = parameter.Attributes.OfType<MinValueAttribute>().SingleOrDefault();
1059+
var maxValue = parameter.Attributes.OfType<MaxValueAttribute>().SingleOrDefault();
10681060

10691061
if (discordType is not (Number or Integer) && (minValue is not null || maxValue is not null))
10701062
{
@@ -1096,8 +1088,8 @@ private static (MinLengthAttribute? MinLength, MaxLengthAttribute? MaxLength) Ge
10961088
ApplicationCommandOptionType discordType
10971089
)
10981090
{
1099-
var minLength = parameter.Parameter.GetCustomAttribute<MinLengthAttribute>();
1100-
var maxLength = parameter.Parameter.GetCustomAttribute<MaxLengthAttribute>();
1091+
var minLength = parameter.Attributes.OfType<MinLengthAttribute>().SingleOrDefault();
1092+
var maxLength = parameter.Attributes.OfType<MaxLengthAttribute>().SingleOrDefault();
11011093

11021094
var isNonStringWithLengthConstraint = discordType is not ApplicationCommandOptionType.String
11031095
&& (minLength is not null || maxLength is not null);

Remora.Discord.Commands/Extensions/ParameterShapeExtensions.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
using System.Collections.Generic;
2525
using System.Linq;
2626
using JetBrains.Annotations;
27-
using Remora.Commands.Extensions;
2827
using Remora.Commands.Signatures;
2928
using Remora.Discord.API.Abstractions.Objects;
3029
using Remora.Discord.Commands.Attributes;
@@ -47,7 +46,7 @@ public static class ParameterShapeExtensions
4746
/// <returns>The actual type.</returns>
4847
public static Type GetActualParameterType(this IParameterShape shape, bool unwrapCollections = false)
4948
{
50-
var parameterType = shape.Parameter.ParameterType;
49+
var parameterType = shape.ParameterType;
5150

5251
// Unwrap the parameter type if it's a Nullable<T> or Optional<T>
5352
// TODO: Maybe more cases?
@@ -81,7 +80,7 @@ public static Type GetActualParameterType(this IParameterShape shape, bool unwra
8180
/// <returns>The option type.</returns>
8281
public static ApplicationCommandOptionType GetDiscordType(this IParameterShape shape)
8382
{
84-
var typeHint = shape.Parameter.GetCustomAttribute<DiscordTypeHintAttribute>();
83+
var typeHint = shape.Attributes.OfType<DiscordTypeHintAttribute>().SingleOrDefault();
8584
if (typeHint is not null)
8685
{
8786
return (ApplicationCommandOptionType)typeHint.TypeHint;

Remora.Discord.Commands/Responders/AutocompleteResponder.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public async Task<Result> RespondAsync(IInteractionCreate gatewayEvent, Cancella
119119
return new InvalidOperationError("Autocomplete interaction without focused option received. Desync?");
120120
}
121121

122-
var realParameter = commandNode.CommandMethod.GetParameters().First
122+
var realParameter = commandNode.Invoke.Method.GetParameters().First
123123
(
124124
p => p.Name is not null && p.Name.Equals(focusedParameter.Name, StringComparison.OrdinalIgnoreCase)
125125
);

Tests/Remora.Discord.Commands.Tests/Extensions/ParameterShapeExtensionsTests.cs

+7-11
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
//
2222

2323
using System;
24+
using System.Collections.Generic;
2425
using System.Reflection;
2526
using Moq;
2627
using Remora.Commands.Signatures;
@@ -52,11 +53,9 @@ public class GetActualParameterType
5253
[Theory]
5354
public void ReturnsCorrectly(Type parameterType, Type expectedResult)
5455
{
55-
var parameterInfoMock = new Mock<ParameterInfo>();
5656
var parameterShapeMock = new Mock<IParameterShape>();
5757

58-
parameterInfoMock.SetupGet(p => p.ParameterType).Returns(parameterType);
59-
parameterShapeMock.SetupGet(p => p.Parameter).Returns(parameterInfoMock.Object);
58+
parameterShapeMock.SetupGet(p => p.ParameterType).Returns(parameterType);
6059

6160
Assert.Equal(expectedResult, parameterShapeMock.Object.GetActualParameterType());
6261
}
@@ -102,11 +101,10 @@ public void ReturnsWithoutTypeHintAttributeCorrectly
102101
ApplicationCommandOptionType expectedResult
103102
)
104103
{
105-
var parameterInfoMock = new Mock<ParameterInfo>();
106104
var parameterShapeMock = new Mock<IParameterShape>();
107105

108-
parameterInfoMock.SetupGet(p => p.ParameterType).Returns(parameterType);
109-
parameterShapeMock.SetupGet(p => p.Parameter).Returns(parameterInfoMock.Object);
106+
parameterShapeMock.SetupGet(p => p.Attributes).Returns(new List<Attribute>());
107+
parameterShapeMock.SetupGet(p => p.ParameterType).Returns(parameterType);
110108

111109
Assert.Equal(expectedResult, parameterShapeMock.Object.GetDiscordType());
112110
}
@@ -132,20 +130,18 @@ ApplicationCommandOptionType expectedResult
132130
[Theory]
133131
public void ReturnsWithTypeHintAttributeCorrectly(Type parameterType)
134132
{
135-
var parameterInfoMock = new Mock<ParameterInfo>();
136133
var parameterShapeMock = new Mock<IParameterShape>();
137134

138-
parameterInfoMock.Setup(p => p.GetCustomAttributes(typeof(DiscordTypeHintAttribute), false))
135+
parameterShapeMock.SetupGet(p => p.Attributes)
139136
.Returns
140137
(
141-
new object[]
138+
new List<Attribute>
142139
{
143140
new DiscordTypeHintAttribute(TypeHint.String)
144141
}
145142
);
146143

147-
parameterInfoMock.SetupGet(p => p.ParameterType).Returns(parameterType);
148-
parameterShapeMock.SetupGet(p => p.Parameter).Returns(parameterInfoMock.Object);
144+
parameterShapeMock.SetupGet(p => p.ParameterType).Returns(parameterType);
149145

150146
Assert.Equal(ApplicationCommandOptionType.String, parameterShapeMock.Object.GetDiscordType());
151147
}

0 commit comments

Comments
 (0)