Interactions Command Localization (#2395)
* Request headers (#2394) * add support for per-request headers * remove unnecessary usings * Revert "remove unnecessary usings" This reverts commit 8d674fe4faf985b117f143fae3877a1698170ad2. * remove nullable strings from RequestOptions * Add Localization Support to Interaction Service (#2211) * add json and resx localization managers * add utils class for getting command paths * update json regex to make langage code optional * remove IServiceProvider from ILocalizationManager method params * replace the command path method in command map * add localization fields to rest and websocket application command entity implementations * move deconstruct extensions method to extensions folder * add withLocalizations parameter to rest methods * fix build error * add rest conversions to interaction service * add localization to the rest methods * add inline docs * fix implementation bugs * add missing inline docs * inline docs correction (Name/Description Localized properties) * add choice localization * fix conflicts * fix conflicts * add missing command props fields to ToApplicationCommandProps methods * add locale parameter to Get*ApplicationCommandsAsync methods for fetching localized command names/descriptions * Apply suggestions from code review Co-authored-by: Armano den Boef <68127614+Rozen4334@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Armano den Boef <68127614+Rozen4334@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Guilds/IGuild.cs Co-authored-by: Armano den Boef <68127614+Rozen4334@users.noreply.github.com> * add inline docs to LocalizationTarget * fix upstream merge errors * fix command parsing for context command names with space char * fix command parsing for context command names with space char * fix failed to generate buket id * fix get guild commands endpoint * update rexs localization manager to use single-file pattern * Upstream Merge Localization Branch (#2434) * fix ci/cd error (#2428) * Fix role icon & emoji assignment. (#2416) * Fix IGuild.GetBansAsync() (#2424) fix the problem of not being able to get more than 1000 bans * [DOCS] Add a note about `DontAutoRegisterAttribute` (#2430) * add a note about `DontAutoRegisterAttribute` * Remove "to to" and add punctuation Co-authored-by: MrCakeSlayer <13650699+MrCakeSlayer@users.noreply.github.com> * fix: Missing Fact attribute in ColorTests (#2425) * feat: Embed comparison (#2347) * Fix broken code snippet in dependency injection docs (#2420) * Fixed markdown formatting to show code snippet * Fixed constructor injection code snippet pointer * Added support for lottie stickers (#2359) Co-authored-by: Armano den Boef <68127614+Rozen4334@users.noreply.github.com> Co-authored-by: BokuNoPasya <49203428+1NieR@users.noreply.github.com> Co-authored-by: Misha133 <61027276+Misha-133@users.noreply.github.com> Co-authored-by: MrCakeSlayer <13650699+MrCakeSlayer@users.noreply.github.com> Co-authored-by: Ge <gehongyan1996@126.com> Co-authored-by: Charlie U <52503242+cpurules@users.noreply.github.com> Co-authored-by: Kuba_Z2 <77853483+KubaZ2@users.noreply.github.com> * remove unnecassary fields from ResxLocalizationManager * update int framework guides * remove space character tokenization from ResxLocalizationManager Co-authored-by: Armano den Boef <68127614+Rozen4334@users.noreply.github.com> Co-authored-by: BokuNoPasya <49203428+1NieR@users.noreply.github.com> Co-authored-by: Misha133 <61027276+Misha-133@users.noreply.github.com> Co-authored-by: MrCakeSlayer <13650699+MrCakeSlayer@users.noreply.github.com> Co-authored-by: Ge <gehongyan1996@126.com> Co-authored-by: Charlie U <52503242+cpurules@users.noreply.github.com> Co-authored-by: Kuba_Z2 <77853483+KubaZ2@users.noreply.github.com>
This commit is contained in:
@@ -376,6 +376,47 @@ respond to the Interactions within your command modules you need to perform the
|
|||||||
delegate can be used to create HTTP responses from a deserialized json object string.
|
delegate can be used to create HTTP responses from a deserialized json object string.
|
||||||
- Use the interaction endpoints of the module base instead of the interaction object (ie. `RespondAsync()`, `FollowupAsync()`...).
|
- Use the interaction endpoints of the module base instead of the interaction object (ie. `RespondAsync()`, `FollowupAsync()`...).
|
||||||
|
|
||||||
|
## Localization
|
||||||
|
|
||||||
|
Discord Slash Commands support name/description localization. Localization is available for names and descriptions of Slash Command Groups ([GroupAttribute]), Slash Commands ([SlashCommandAttribute]), Slash Command parameters and Slash Command Parameter Choices. Interaction Service can be initialized with an `ILocalizationManager` instance in its config which is used to create the necessary localization dictionaries on command registration. Interaction Service has two built-in `ILocalizationManager` implementations: `ResxLocalizationManager` and `JsonLocalizationManager`.
|
||||||
|
|
||||||
|
### ResXLocalizationManager
|
||||||
|
|
||||||
|
`ResxLocalizationManager` uses `.` delimited key names to traverse the resource files and get the localized strings (`group1.group2.command.parameter.name`). A `ResxLocalizationManager` instance must be initialized with a base resource name, a target assembly and a collection of `CultureInfo`s. Every key path must end with either `.name` or `.description`, including parameter choice strings. [Discord.Tools.LocalizationTemplate.Resx](https://www.nuget.org/packages/Discord.Tools.LocalizationTemplate.Resx) dotnet tool can be used to create localization file templates.
|
||||||
|
|
||||||
|
### JsonLocalizationManager
|
||||||
|
|
||||||
|
`JsonLocaliationManager` uses a nested data structure similar to Discord's Application Commands schema. You can get the Json schema [here](https://gist.github.com/Cenngo/d46a881de24823302f66c3c7e2f7b254). `JsonLocalizationManager` accepts a base path and a base file name and automatically discovers every resource file ( \basePath\fileName.locale.json ). A Json resource file should have a structure similar to:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"command_1":{
|
||||||
|
"name": "localized_name",
|
||||||
|
"description": "localized_description",
|
||||||
|
"parameter_1":{
|
||||||
|
"name": "localized_name",
|
||||||
|
"description": "localized_description"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"group_1":{
|
||||||
|
"name": "localized_name",
|
||||||
|
"description": "localized_description",
|
||||||
|
"command_1":{
|
||||||
|
"name": "localized_name",
|
||||||
|
"description": "localized_description",
|
||||||
|
"parameter_1":{
|
||||||
|
"name": "localized_name",
|
||||||
|
"description": "localized_description"
|
||||||
|
},
|
||||||
|
"parameter_2":{
|
||||||
|
"name": "localized_name",
|
||||||
|
"description": "localized_description"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
[AutocompleteHandlers]: xref:Guides.IntFw.AutoCompletion
|
[AutocompleteHandlers]: xref:Guides.IntFw.AutoCompletion
|
||||||
[DependencyInjection]: xref:Guides.DI.Intro
|
[DependencyInjection]: xref:Guides.DI.Intro
|
||||||
|
|
||||||
|
|||||||
@@ -1194,12 +1194,17 @@ namespace Discord
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets this guilds application commands.
|
/// Gets this guilds application commands.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="withLocalizations">
|
||||||
|
/// Whether to include full localization dictionaries in the returned objects,
|
||||||
|
/// instead of the localized name and description fields.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="locale">The target locale of the localized name and description fields. Sets the <c>X-Discord-Locale</c> header, which takes precedence over <c>Accept-Language</c>.</param>
|
||||||
/// <param name="options">The options to be used when sending the request.</param>
|
/// <param name="options">The options to be used when sending the request.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// A task that represents the asynchronous get operation. The task result contains a read-only collection
|
/// A task that represents the asynchronous get operation. The task result contains a read-only collection
|
||||||
/// of application commands found within the guild.
|
/// of application commands found within the guild.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
Task<IReadOnlyCollection<IApplicationCommand>> GetApplicationCommandsAsync(RequestOptions options = null);
|
Task<IReadOnlyCollection<IApplicationCommand>> GetApplicationCommandsAsync(bool withLocalizations = false, string locale = null, RequestOptions options = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an application command within this guild with the specified id.
|
/// Gets an application command within this guild with the specified id.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
@@ -12,6 +13,8 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
private string _name;
|
private string _name;
|
||||||
private string _description;
|
private string _description;
|
||||||
|
private IDictionary<string, string> _nameLocalizations = new Dictionary<string, string>();
|
||||||
|
private IDictionary<string, string> _descriptionLocalizations = new Dictionary<string, string>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the name of this option.
|
/// Gets or sets the name of this option.
|
||||||
@@ -21,18 +24,7 @@ namespace Discord
|
|||||||
get => _name;
|
get => _name;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == null)
|
EnsureValidOptionName(value);
|
||||||
throw new ArgumentNullException(nameof(value), $"{nameof(Name)} cannot be null.");
|
|
||||||
|
|
||||||
if (value.Length > 32)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(value), "Name length must be less than or equal to 32.");
|
|
||||||
|
|
||||||
if (!Regex.IsMatch(value, @"^[\w-]{1,32}$"))
|
|
||||||
throw new FormatException($"{nameof(value)} must match the regex ^[\\w-]{{1,32}}$");
|
|
||||||
|
|
||||||
if (value.Any(x => char.IsUpper(x)))
|
|
||||||
throw new FormatException("Name cannot contain any uppercase characters.");
|
|
||||||
|
|
||||||
_name = value;
|
_name = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,12 +35,11 @@ namespace Discord
|
|||||||
public string Description
|
public string Description
|
||||||
{
|
{
|
||||||
get => _description;
|
get => _description;
|
||||||
set => _description = value?.Length switch
|
set
|
||||||
{
|
{
|
||||||
> 100 => throw new ArgumentOutOfRangeException(nameof(value), "Description length must be less than or equal to 100."),
|
EnsureValidOptionDescription(value);
|
||||||
0 => throw new ArgumentOutOfRangeException(nameof(value), "Description length must be at least 1."),
|
_description = value;
|
||||||
_ => value
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -105,5 +96,72 @@ namespace Discord
|
|||||||
/// Gets or sets the allowed channel types for this option.
|
/// Gets or sets the allowed channel types for this option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<ChannelType> ChannelTypes { get; set; }
|
public List<ChannelType> ChannelTypes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the localization dictionary for the name field of this option.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when any of the dictionary keys is an invalid locale.</exception>
|
||||||
|
public IDictionary<string, string> NameLocalizations
|
||||||
|
{
|
||||||
|
get => _nameLocalizations;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
foreach (var (locale, name) in value)
|
||||||
|
{
|
||||||
|
if(!Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException($"Invalid locale: {locale}", nameof(locale));
|
||||||
|
|
||||||
|
EnsureValidOptionName(name);
|
||||||
|
}
|
||||||
|
_nameLocalizations = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the localization dictionary for the description field of this option.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when any of the dictionary keys is an invalid locale.</exception>
|
||||||
|
public IDictionary<string, string> DescriptionLocalizations
|
||||||
|
{
|
||||||
|
get => _descriptionLocalizations;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
foreach (var (locale, description) in value)
|
||||||
|
{
|
||||||
|
if(!Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException($"Invalid locale: {locale}", nameof(locale));
|
||||||
|
|
||||||
|
EnsureValidOptionDescription(description);
|
||||||
|
}
|
||||||
|
_descriptionLocalizations = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureValidOptionName(string name)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
throw new ArgumentNullException(nameof(name), $"{nameof(Name)} cannot be null.");
|
||||||
|
|
||||||
|
if (name.Length > 32)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(name), "Name length must be less than or equal to 32.");
|
||||||
|
|
||||||
|
if (!Regex.IsMatch(name, @"^[\w-]{1,32}$"))
|
||||||
|
throw new FormatException($"{nameof(name)} must match the regex ^[\\w-]{{1,32}}$");
|
||||||
|
|
||||||
|
if (name.Any(x => char.IsUpper(x)))
|
||||||
|
throw new FormatException("Name cannot contain any uppercase characters.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureValidOptionDescription(string description)
|
||||||
|
{
|
||||||
|
switch (description.Length)
|
||||||
|
{
|
||||||
|
case > 100:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(description),
|
||||||
|
"Description length must be less than or equal to 100.");
|
||||||
|
case 0:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(description), "Description length must at least 1.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace Discord
|
namespace Discord
|
||||||
{
|
{
|
||||||
@@ -9,6 +13,7 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
private string _name;
|
private string _name;
|
||||||
private object _value;
|
private object _value;
|
||||||
|
private IDictionary<string, string> _nameLocalizations = new Dictionary<string, string>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the name of this choice.
|
/// Gets or sets the name of this choice.
|
||||||
@@ -40,5 +45,33 @@ namespace Discord
|
|||||||
_value = value;
|
_value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the localization dictionary for the name field of this choice.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when any of the dictionary keys is an invalid locale.</exception>
|
||||||
|
public IDictionary<string, string> NameLocalizations
|
||||||
|
{
|
||||||
|
get => _nameLocalizations;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
foreach (var (locale, name) in value)
|
||||||
|
{
|
||||||
|
if (!Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException("Key values of the dictionary must be valid language codes.");
|
||||||
|
|
||||||
|
switch (name.Length)
|
||||||
|
{
|
||||||
|
case > 100:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value),
|
||||||
|
"Name length must be less than or equal to 100.");
|
||||||
|
case 0:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value), "Name length must at least 1.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_nameLocalizations = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace Discord
|
namespace Discord
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -5,6 +12,9 @@ namespace Discord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class ApplicationCommandProperties
|
public abstract class ApplicationCommandProperties
|
||||||
{
|
{
|
||||||
|
private IReadOnlyDictionary<string, string> _nameLocalizations;
|
||||||
|
private IReadOnlyDictionary<string, string> _descriptionLocalizations;
|
||||||
|
|
||||||
internal abstract ApplicationCommandType Type { get; }
|
internal abstract ApplicationCommandType Type { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -17,6 +27,48 @@ namespace Discord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Optional<bool> IsDefaultPermission { get; set; }
|
public Optional<bool> IsDefaultPermission { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the localization dictionary for the name field of this command.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> NameLocalizations
|
||||||
|
{
|
||||||
|
get => _nameLocalizations;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
foreach (var (locale, name) in value)
|
||||||
|
{
|
||||||
|
if(!Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException($"Invalid locale: {locale}", nameof(locale));
|
||||||
|
|
||||||
|
Preconditions.AtLeast(name.Length, 1, nameof(name));
|
||||||
|
Preconditions.AtMost(name.Length, SlashCommandBuilder.MaxNameLength, nameof(name));
|
||||||
|
if (!Regex.IsMatch(name, @"^[\w-]{1,32}$"))
|
||||||
|
throw new ArgumentException("Option name cannot contain any special characters or whitespaces!", nameof(name));
|
||||||
|
}
|
||||||
|
_nameLocalizations = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the localization dictionary for the description field of this command.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> DescriptionLocalizations
|
||||||
|
{
|
||||||
|
get => _descriptionLocalizations;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
foreach (var (locale, description) in value)
|
||||||
|
{
|
||||||
|
if(!Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException($"Invalid locale: {locale}", nameof(locale));
|
||||||
|
|
||||||
|
Preconditions.AtLeast(description.Length, 1, nameof(description));
|
||||||
|
Preconditions.AtMost(description.Length, SlashCommandBuilder.MaxDescriptionLength, nameof(description));
|
||||||
|
}
|
||||||
|
_descriptionLocalizations = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets whether or not this command can be used in DMs.
|
/// Gets or sets whether or not this command can be used in DMs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace Discord
|
namespace Discord
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -31,6 +36,11 @@ namespace Discord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsDefaultPermission { get; set; } = true;
|
public bool IsDefaultPermission { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the name field of this command.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> NameLocalizations => _nameLocalizations;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets whether or not this command can be used in DMs.
|
/// Gets or sets whether or not this command can be used in DMs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -42,6 +52,7 @@ namespace Discord
|
|||||||
public GuildPermission? DefaultMemberPermissions { get; set; }
|
public GuildPermission? DefaultMemberPermissions { get; set; }
|
||||||
|
|
||||||
private string _name;
|
private string _name;
|
||||||
|
private Dictionary<string, string> _nameLocalizations;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Build the current builder into a <see cref="MessageCommandProperties"/> class.
|
/// Build the current builder into a <see cref="MessageCommandProperties"/> class.
|
||||||
@@ -86,6 +97,30 @@ namespace Discord
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the <see cref="NameLocalizations"/> collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nameLocalizations">The localization dictionary to use for the name field of this command.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown if <paramref name="nameLocalizations"/> is null.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if any dictionary key is an invalid locale string.</exception>
|
||||||
|
public MessageCommandBuilder WithNameLocalizations(IDictionary<string, string> nameLocalizations)
|
||||||
|
{
|
||||||
|
if (nameLocalizations is null)
|
||||||
|
throw new ArgumentNullException(nameof(nameLocalizations));
|
||||||
|
|
||||||
|
foreach (var (locale, name) in nameLocalizations)
|
||||||
|
{
|
||||||
|
if(!Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException($"Invalid locale: {locale}", nameof(locale));
|
||||||
|
|
||||||
|
EnsureValidCommandName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
_nameLocalizations = new Dictionary<string, string>(nameLocalizations);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets whether or not this command can be used in dms
|
/// Sets whether or not this command can be used in dms
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -97,6 +132,41 @@ namespace Discord
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new entry to the <see cref="NameLocalizations"/> collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="locale">Locale of the entry.</param>
|
||||||
|
/// <param name="name">Localized string for the name field.</param>
|
||||||
|
/// <returns>The current builder.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if <paramref name="locale"/> is an invalid locale string.</exception>
|
||||||
|
public MessageCommandBuilder AddNameLocalization(string locale, string name)
|
||||||
|
{
|
||||||
|
if(!Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException($"Invalid locale: {locale}", nameof(locale));
|
||||||
|
|
||||||
|
EnsureValidCommandName(name);
|
||||||
|
|
||||||
|
_nameLocalizations ??= new();
|
||||||
|
_nameLocalizations.Add(locale, name);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureValidCommandName(string name)
|
||||||
|
{
|
||||||
|
Preconditions.NotNullOrEmpty(name, nameof(name));
|
||||||
|
Preconditions.AtLeast(name.Length, 1, nameof(name));
|
||||||
|
Preconditions.AtMost(name.Length, MaxNameLength, nameof(name));
|
||||||
|
|
||||||
|
// Discord updated the docs, this regex prevents special characters like @!$%(... etc,
|
||||||
|
// https://discord.com/developers/docs/interactions/slash-commands#applicationcommand
|
||||||
|
if (!Regex.IsMatch(name, @"^[\w-]{1,32}$"))
|
||||||
|
throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(name));
|
||||||
|
|
||||||
|
if (name.Any(x => char.IsUpper(x)))
|
||||||
|
throw new FormatException("Name cannot contain any uppercase characters.");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the default member permissions required to use this application command.
|
/// Sets the default member permissions required to use this application command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace Discord
|
namespace Discord
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -5,7 +10,7 @@ namespace Discord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class UserCommandBuilder
|
public class UserCommandBuilder
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the maximum length a commands name allowed by Discord.
|
/// Returns the maximum length a commands name allowed by Discord.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int MaxNameLength = 32;
|
public const int MaxNameLength = 32;
|
||||||
@@ -31,6 +36,11 @@ namespace Discord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsDefaultPermission { get; set; } = true;
|
public bool IsDefaultPermission { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the name field of this command.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> NameLocalizations => _nameLocalizations;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets whether or not this command can be used in DMs.
|
/// Gets or sets whether or not this command can be used in DMs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -42,6 +52,7 @@ namespace Discord
|
|||||||
public GuildPermission? DefaultMemberPermissions { get; set; }
|
public GuildPermission? DefaultMemberPermissions { get; set; }
|
||||||
|
|
||||||
private string _name;
|
private string _name;
|
||||||
|
private Dictionary<string, string> _nameLocalizations;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Build the current builder into a <see cref="UserCommandProperties"/> class.
|
/// Build the current builder into a <see cref="UserCommandProperties"/> class.
|
||||||
@@ -84,6 +95,30 @@ namespace Discord
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the <see cref="NameLocalizations"/> collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nameLocalizations">The localization dictionary to use for the name field of this command.</param>
|
||||||
|
/// <returns>The current builder.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown if <paramref name="nameLocalizations"/> is null.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if any dictionary key is an invalid locale string.</exception>
|
||||||
|
public UserCommandBuilder WithNameLocalizations(IDictionary<string, string> nameLocalizations)
|
||||||
|
{
|
||||||
|
if (nameLocalizations is null)
|
||||||
|
throw new ArgumentNullException(nameof(nameLocalizations));
|
||||||
|
|
||||||
|
foreach (var (locale, name) in nameLocalizations)
|
||||||
|
{
|
||||||
|
if(!Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException($"Invalid locale: {locale}", nameof(locale));
|
||||||
|
|
||||||
|
EnsureValidCommandName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
_nameLocalizations = new Dictionary<string, string>(nameLocalizations);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets whether or not this command can be used in dms
|
/// Sets whether or not this command can be used in dms
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -95,6 +130,41 @@ namespace Discord
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new entry to the <see cref="NameLocalizations"/> collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="locale">Locale of the entry.</param>
|
||||||
|
/// <param name="name">Localized string for the name field.</param>
|
||||||
|
/// <returns>The current builder.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if <paramref name="locale"/> is an invalid locale string.</exception>
|
||||||
|
public UserCommandBuilder AddNameLocalization(string locale, string name)
|
||||||
|
{
|
||||||
|
if(!Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException($"Invalid locale: {locale}", nameof(locale));
|
||||||
|
|
||||||
|
EnsureValidCommandName(name);
|
||||||
|
|
||||||
|
_nameLocalizations ??= new();
|
||||||
|
_nameLocalizations.Add(locale, name);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureValidCommandName(string name)
|
||||||
|
{
|
||||||
|
Preconditions.NotNullOrEmpty(name, nameof(name));
|
||||||
|
Preconditions.AtLeast(name.Length, 1, nameof(name));
|
||||||
|
Preconditions.AtMost(name.Length, MaxNameLength, nameof(name));
|
||||||
|
|
||||||
|
// Discord updated the docs, this regex prevents special characters like @!$%(... etc,
|
||||||
|
// https://discord.com/developers/docs/interactions/slash-commands#applicationcommand
|
||||||
|
if (!Regex.IsMatch(name, @"^[\w-]{1,32}$"))
|
||||||
|
throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(name));
|
||||||
|
|
||||||
|
if (name.Any(x => char.IsUpper(x)))
|
||||||
|
throw new FormatException("Name cannot contain any uppercase characters.");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the default member permissions required to use this application command.
|
/// Sets the default member permissions required to use this application command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -52,6 +52,32 @@ namespace Discord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
IReadOnlyCollection<IApplicationCommandOption> Options { get; }
|
IReadOnlyCollection<IApplicationCommandOption> Options { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the name field of this command.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyDictionary<string, string> NameLocalizations { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the description field of this command.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyDictionary<string, string> DescriptionLocalizations { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized name of this command.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only returned when the `withLocalizations` query parameter is set to <see langword="false"/> when requesting the command.
|
||||||
|
/// </remarks>
|
||||||
|
string NameLocalized { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized description of this command.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only returned when the `withLocalizations` query parameter is set to <see langword="false"/> when requesting the command.
|
||||||
|
/// </remarks>
|
||||||
|
string DescriptionLocalized { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Modifies the current application command.
|
/// Modifies the current application command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -71,5 +71,31 @@ namespace Discord
|
|||||||
/// Gets the allowed channel types for this option.
|
/// Gets the allowed channel types for this option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IReadOnlyCollection<ChannelType> ChannelTypes { get; }
|
IReadOnlyCollection<ChannelType> ChannelTypes { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the name field of this command option.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyDictionary<string, string> NameLocalizations { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the description field of this command option.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyDictionary<string, string> DescriptionLocalizations { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized name of this command option.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only returned when the `withLocalizations` query parameter is set to <see langword="false"/> when requesting the command.
|
||||||
|
/// </remarks>
|
||||||
|
string NameLocalized { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized description of this command option.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only returned when the `withLocalizations` query parameter is set to true when requesting the command.
|
||||||
|
/// </remarks>
|
||||||
|
string DescriptionLocalized { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Discord
|
namespace Discord
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -14,5 +16,18 @@ namespace Discord
|
|||||||
/// Gets the value of the choice.
|
/// Gets the value of the choice.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
object Value { get; }
|
object Value { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the name field of this command option.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyDictionary<string, string> NameLocalizations { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized name of this command option.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only returned when the `withLocalizations` query parameter is set to <see langword="false"/> when requesting the command.
|
||||||
|
/// </remarks>
|
||||||
|
string NameLocalized { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Sockets;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace Discord
|
namespace Discord
|
||||||
@@ -31,18 +34,7 @@ namespace Discord
|
|||||||
get => _name;
|
get => _name;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
Preconditions.NotNullOrEmpty(value, nameof(value));
|
EnsureValidCommandName(value);
|
||||||
Preconditions.AtLeast(value.Length, 1, nameof(value));
|
|
||||||
Preconditions.AtMost(value.Length, MaxNameLength, nameof(value));
|
|
||||||
|
|
||||||
// Discord updated the docs, this regex prevents special characters like @!$%(... etc,
|
|
||||||
// https://discord.com/developers/docs/interactions/slash-commands#applicationcommand
|
|
||||||
if (!Regex.IsMatch(value, @"^[\w-]{1,32}$"))
|
|
||||||
throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(value));
|
|
||||||
|
|
||||||
if (value.Any(x => char.IsUpper(x)))
|
|
||||||
throw new FormatException("Name cannot contain any uppercase characters.");
|
|
||||||
|
|
||||||
_name = value;
|
_name = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,10 +47,7 @@ namespace Discord
|
|||||||
get => _description;
|
get => _description;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
Preconditions.NotNullOrEmpty(value, nameof(Description));
|
EnsureValidCommandDescription(value);
|
||||||
Preconditions.AtLeast(value.Length, 1, nameof(Description));
|
|
||||||
Preconditions.AtMost(value.Length, MaxDescriptionLength, nameof(Description));
|
|
||||||
|
|
||||||
_description = value;
|
_description = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,6 +65,16 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the name field of this command.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> NameLocalizations => _nameLocalizations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the description field of this command.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> DescriptionLocalizations => _descriptionLocalizations;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets whether the command is enabled by default when the app is added to a guild
|
/// Gets or sets whether the command is enabled by default when the app is added to a guild
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -93,6 +92,8 @@ namespace Discord
|
|||||||
|
|
||||||
private string _name;
|
private string _name;
|
||||||
private string _description;
|
private string _description;
|
||||||
|
private Dictionary<string, string> _nameLocalizations;
|
||||||
|
private Dictionary<string, string> _descriptionLocalizations;
|
||||||
private List<SlashCommandOptionBuilder> _options;
|
private List<SlashCommandOptionBuilder> _options;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -106,6 +107,8 @@ namespace Discord
|
|||||||
Name = Name,
|
Name = Name,
|
||||||
Description = Description,
|
Description = Description,
|
||||||
IsDefaultPermission = IsDefaultPermission,
|
IsDefaultPermission = IsDefaultPermission,
|
||||||
|
NameLocalizations = _nameLocalizations,
|
||||||
|
DescriptionLocalizations = _descriptionLocalizations,
|
||||||
IsDMEnabled = IsDMEnabled,
|
IsDMEnabled = IsDMEnabled,
|
||||||
DefaultMemberPermissions = DefaultMemberPermissions ?? Optional<GuildPermission>.Unspecified
|
DefaultMemberPermissions = DefaultMemberPermissions ?? Optional<GuildPermission>.Unspecified
|
||||||
};
|
};
|
||||||
@@ -190,13 +193,17 @@ namespace Discord
|
|||||||
/// <param name="isAutocomplete">If this option is set to autocomplete.</param>
|
/// <param name="isAutocomplete">If this option is set to autocomplete.</param>
|
||||||
/// <param name="options">The options of the option to add.</param>
|
/// <param name="options">The options of the option to add.</param>
|
||||||
/// <param name="channelTypes">The allowed channel types for this option.</param>
|
/// <param name="channelTypes">The allowed channel types for this option.</param>
|
||||||
|
/// <param name="nameLocalizations">Localization dictionary for the name field of this command.</param>
|
||||||
|
/// <param name="descriptionLocalizations">Localization dictionary for the description field of this command.</param>
|
||||||
/// <param name="choices">The choices of this option.</param>
|
/// <param name="choices">The choices of this option.</param>
|
||||||
/// <param name="minValue">The smallest number value the user can input.</param>
|
/// <param name="minValue">The smallest number value the user can input.</param>
|
||||||
/// <param name="maxValue">The largest number value the user can input.</param>
|
/// <param name="maxValue">The largest number value the user can input.</param>
|
||||||
/// <returns>The current builder.</returns>
|
/// <returns>The current builder.</returns>
|
||||||
public SlashCommandBuilder AddOption(string name, ApplicationCommandOptionType type,
|
public SlashCommandBuilder AddOption(string name, ApplicationCommandOptionType type,
|
||||||
string description, bool? isRequired = null, bool? isDefault = null, bool isAutocomplete = false, double? minValue = null, double? maxValue = null,
|
string description, bool? isRequired = null, bool? isDefault = null, bool isAutocomplete = false, double? minValue = null, double? maxValue = null,
|
||||||
int? minLength = null, int? maxLength = null, List<SlashCommandOptionBuilder> options = null, List<ChannelType> channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices)
|
List<SlashCommandOptionBuilder> options = null, List<ChannelType> channelTypes = null, IDictionary<string, string> nameLocalizations = null,
|
||||||
|
IDictionary<string, string> descriptionLocalizations = null,
|
||||||
|
int? minLength = null, int? maxLength = null, params ApplicationCommandOptionChoiceProperties[] choices)
|
||||||
{
|
{
|
||||||
Preconditions.Options(name, description);
|
Preconditions.Options(name, description);
|
||||||
|
|
||||||
@@ -226,6 +233,12 @@ namespace Discord
|
|||||||
MaxLength = maxLength,
|
MaxLength = maxLength,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (nameLocalizations is not null)
|
||||||
|
option.WithNameLocalizations(nameLocalizations);
|
||||||
|
|
||||||
|
if (descriptionLocalizations is not null)
|
||||||
|
option.WithDescriptionLocalizations(descriptionLocalizations);
|
||||||
|
|
||||||
return AddOption(option);
|
return AddOption(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,6 +281,116 @@ namespace Discord
|
|||||||
Options.AddRange(options);
|
Options.AddRange(options);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the <see cref="NameLocalizations"/> collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nameLocalizations">The localization dictionary to use for the name field of this command.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown if <paramref name="nameLocalizations"/> is null.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if any dictionary key is an invalid locale string.</exception>
|
||||||
|
public SlashCommandBuilder WithNameLocalizations(IDictionary<string, string> nameLocalizations)
|
||||||
|
{
|
||||||
|
if (nameLocalizations is null)
|
||||||
|
throw new ArgumentNullException(nameof(nameLocalizations));
|
||||||
|
|
||||||
|
foreach (var (locale, name) in nameLocalizations)
|
||||||
|
{
|
||||||
|
if(!Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException($"Invalid locale: {locale}", nameof(locale));
|
||||||
|
|
||||||
|
EnsureValidCommandName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
_nameLocalizations = new Dictionary<string, string>(nameLocalizations);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the <see cref="DescriptionLocalizations"/> collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="descriptionLocalizations">The localization dictionary to use for the description field of this command.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown if <paramref name="descriptionLocalizations"/> is null.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if any dictionary key is an invalid locale string.</exception>
|
||||||
|
public SlashCommandBuilder WithDescriptionLocalizations(IDictionary<string, string> descriptionLocalizations)
|
||||||
|
{
|
||||||
|
if (descriptionLocalizations is null)
|
||||||
|
throw new ArgumentNullException(nameof(descriptionLocalizations));
|
||||||
|
|
||||||
|
foreach (var (locale, description) in descriptionLocalizations)
|
||||||
|
{
|
||||||
|
if(!Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException($"Invalid locale: {locale}", nameof(locale));
|
||||||
|
|
||||||
|
EnsureValidCommandDescription(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
_descriptionLocalizations = new Dictionary<string, string>(descriptionLocalizations);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new entry to the <see cref="NameLocalizations"/> collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="locale">Locale of the entry.</param>
|
||||||
|
/// <param name="name">Localized string for the name field.</param>
|
||||||
|
/// <returns>The current builder.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if <paramref name="locale"/> is an invalid locale string.</exception>
|
||||||
|
public SlashCommandBuilder AddNameLocalization(string locale, string name)
|
||||||
|
{
|
||||||
|
if(!Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException($"Invalid locale: {locale}", nameof(locale));
|
||||||
|
|
||||||
|
EnsureValidCommandName(name);
|
||||||
|
|
||||||
|
_nameLocalizations ??= new();
|
||||||
|
_nameLocalizations.Add(locale, name);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new entry to the <see cref="Description"/> collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="locale">Locale of the entry.</param>
|
||||||
|
/// <param name="description">Localized string for the description field.</param>
|
||||||
|
/// <returns>The current builder.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if <paramref name="locale"/> is an invalid locale string.</exception>
|
||||||
|
public SlashCommandBuilder AddDescriptionLocalization(string locale, string description)
|
||||||
|
{
|
||||||
|
if(!Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException($"Invalid locale: {locale}", nameof(locale));
|
||||||
|
|
||||||
|
EnsureValidCommandDescription(description);
|
||||||
|
|
||||||
|
_descriptionLocalizations ??= new();
|
||||||
|
_descriptionLocalizations.Add(locale, description);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void EnsureValidCommandName(string name)
|
||||||
|
{
|
||||||
|
Preconditions.NotNullOrEmpty(name, nameof(name));
|
||||||
|
Preconditions.AtLeast(name.Length, 1, nameof(name));
|
||||||
|
Preconditions.AtMost(name.Length, MaxNameLength, nameof(name));
|
||||||
|
|
||||||
|
// Discord updated the docs, this regex prevents special characters like @!$%(... etc,
|
||||||
|
// https://discord.com/developers/docs/interactions/slash-commands#applicationcommand
|
||||||
|
if (!Regex.IsMatch(name, @"^[\w-]{1,32}$"))
|
||||||
|
throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(name));
|
||||||
|
|
||||||
|
if (name.Any(x => char.IsUpper(x)))
|
||||||
|
throw new FormatException("Name cannot contain any uppercase characters.");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void EnsureValidCommandDescription(string description)
|
||||||
|
{
|
||||||
|
Preconditions.NotNullOrEmpty(description, nameof(description));
|
||||||
|
Preconditions.AtLeast(description.Length, 1, nameof(description));
|
||||||
|
Preconditions.AtMost(description.Length, MaxDescriptionLength, nameof(description));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -287,6 +410,8 @@ namespace Discord
|
|||||||
|
|
||||||
private string _name;
|
private string _name;
|
||||||
private string _description;
|
private string _description;
|
||||||
|
private Dictionary<string, string> _nameLocalizations;
|
||||||
|
private Dictionary<string, string> _descriptionLocalizations;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the name of this option.
|
/// Gets or sets the name of this option.
|
||||||
@@ -298,10 +423,7 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
Preconditions.AtLeast(value.Length, 1, nameof(value));
|
EnsureValidCommandOptionName(value);
|
||||||
Preconditions.AtMost(value.Length, SlashCommandBuilder.MaxNameLength, nameof(value));
|
|
||||||
if (!Regex.IsMatch(value, @"^[\w-]{1,32}$"))
|
|
||||||
throw new ArgumentException("Option name cannot contain any special characters or whitespaces!", nameof(value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_name = value;
|
_name = value;
|
||||||
@@ -318,8 +440,7 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
Preconditions.AtLeast(value.Length, 1, nameof(value));
|
EnsureValidCommandOptionDescription(value);
|
||||||
Preconditions.AtMost(value.Length, SlashCommandBuilder.MaxDescriptionLength, nameof(value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_description = value;
|
_description = value;
|
||||||
@@ -381,6 +502,16 @@ namespace Discord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public List<ChannelType> ChannelTypes { get; set; }
|
public List<ChannelType> ChannelTypes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the name field of this command.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> NameLocalizations => _nameLocalizations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the description field of this command.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> DescriptionLocalizations => _descriptionLocalizations;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Builds the current option.
|
/// Builds the current option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -424,6 +555,8 @@ namespace Discord
|
|||||||
ChannelTypes = ChannelTypes,
|
ChannelTypes = ChannelTypes,
|
||||||
MinValue = MinValue,
|
MinValue = MinValue,
|
||||||
MaxValue = MaxValue,
|
MaxValue = MaxValue,
|
||||||
|
NameLocalizations = _nameLocalizations,
|
||||||
|
DescriptionLocalizations = _descriptionLocalizations,
|
||||||
MinLength = MinLength,
|
MinLength = MinLength,
|
||||||
MaxLength = MaxLength,
|
MaxLength = MaxLength,
|
||||||
};
|
};
|
||||||
@@ -440,13 +573,17 @@ namespace Discord
|
|||||||
/// <param name="isAutocomplete">If this option supports autocomplete.</param>
|
/// <param name="isAutocomplete">If this option supports autocomplete.</param>
|
||||||
/// <param name="options">The options of the option to add.</param>
|
/// <param name="options">The options of the option to add.</param>
|
||||||
/// <param name="channelTypes">The allowed channel types for this option.</param>
|
/// <param name="channelTypes">The allowed channel types for this option.</param>
|
||||||
|
/// <param name="nameLocalizations">Localization dictionary for the description field of this command.</param>
|
||||||
|
/// <param name="descriptionLocalizations">Localization dictionary for the description field of this command.</param>
|
||||||
/// <param name="choices">The choices of this option.</param>
|
/// <param name="choices">The choices of this option.</param>
|
||||||
/// <param name="minValue">The smallest number value the user can input.</param>
|
/// <param name="minValue">The smallest number value the user can input.</param>
|
||||||
/// <param name="maxValue">The largest number value the user can input.</param>
|
/// <param name="maxValue">The largest number value the user can input.</param>
|
||||||
/// <returns>The current builder.</returns>
|
/// <returns>The current builder.</returns>
|
||||||
public SlashCommandOptionBuilder AddOption(string name, ApplicationCommandOptionType type,
|
public SlashCommandOptionBuilder AddOption(string name, ApplicationCommandOptionType type,
|
||||||
string description, bool? isRequired = null, bool isDefault = false, bool isAutocomplete = false, double? minValue = null, double? maxValue = null,
|
string description, bool? isRequired = null, bool isDefault = false, bool isAutocomplete = false, double? minValue = null, double? maxValue = null,
|
||||||
int? minLength = null, int? maxLength = null, List<SlashCommandOptionBuilder> options = null, List<ChannelType> channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices)
|
List<SlashCommandOptionBuilder> options = null, List<ChannelType> channelTypes = null, IDictionary<string, string> nameLocalizations = null,
|
||||||
|
IDictionary<string, string> descriptionLocalizations = null,
|
||||||
|
int? minLength = null, int? maxLength = null, params ApplicationCommandOptionChoiceProperties[] choices)
|
||||||
{
|
{
|
||||||
Preconditions.Options(name, description);
|
Preconditions.Options(name, description);
|
||||||
|
|
||||||
@@ -473,9 +610,15 @@ namespace Discord
|
|||||||
Options = options,
|
Options = options,
|
||||||
Type = type,
|
Type = type,
|
||||||
Choices = (choices ?? Array.Empty<ApplicationCommandOptionChoiceProperties>()).ToList(),
|
Choices = (choices ?? Array.Empty<ApplicationCommandOptionChoiceProperties>()).ToList(),
|
||||||
ChannelTypes = channelTypes
|
ChannelTypes = channelTypes,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if(nameLocalizations is not null)
|
||||||
|
option.WithNameLocalizations(nameLocalizations);
|
||||||
|
|
||||||
|
if(descriptionLocalizations is not null)
|
||||||
|
option.WithDescriptionLocalizations(descriptionLocalizations);
|
||||||
|
|
||||||
return AddOption(option);
|
return AddOption(option);
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -522,10 +665,11 @@ namespace Discord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name of the choice.</param>
|
/// <param name="name">The name of the choice.</param>
|
||||||
/// <param name="value">The value of the choice.</param>
|
/// <param name="value">The value of the choice.</param>
|
||||||
|
/// <param name="nameLocalizations">The localization dictionary for to use the name field of this command option choice.</param>
|
||||||
/// <returns>The current builder.</returns>
|
/// <returns>The current builder.</returns>
|
||||||
public SlashCommandOptionBuilder AddChoice(string name, int value)
|
public SlashCommandOptionBuilder AddChoice(string name, int value, IDictionary<string, string> nameLocalizations = null)
|
||||||
{
|
{
|
||||||
return AddChoiceInternal(name, value);
|
return AddChoiceInternal(name, value, nameLocalizations);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -533,10 +677,11 @@ namespace Discord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name of the choice.</param>
|
/// <param name="name">The name of the choice.</param>
|
||||||
/// <param name="value">The value of the choice.</param>
|
/// <param name="value">The value of the choice.</param>
|
||||||
|
/// <param name="nameLocalizations">The localization dictionary for to use the name field of this command option choice.</param>
|
||||||
/// <returns>The current builder.</returns>
|
/// <returns>The current builder.</returns>
|
||||||
public SlashCommandOptionBuilder AddChoice(string name, string value)
|
public SlashCommandOptionBuilder AddChoice(string name, string value, IDictionary<string, string> nameLocalizations = null)
|
||||||
{
|
{
|
||||||
return AddChoiceInternal(name, value);
|
return AddChoiceInternal(name, value, nameLocalizations);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -544,10 +689,11 @@ namespace Discord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name of the choice.</param>
|
/// <param name="name">The name of the choice.</param>
|
||||||
/// <param name="value">The value of the choice.</param>
|
/// <param name="value">The value of the choice.</param>
|
||||||
|
/// <param name="nameLocalizations">Localization dictionary for the description field of this command.</param>
|
||||||
/// <returns>The current builder.</returns>
|
/// <returns>The current builder.</returns>
|
||||||
public SlashCommandOptionBuilder AddChoice(string name, double value)
|
public SlashCommandOptionBuilder AddChoice(string name, double value, IDictionary<string, string> nameLocalizations = null)
|
||||||
{
|
{
|
||||||
return AddChoiceInternal(name, value);
|
return AddChoiceInternal(name, value, nameLocalizations);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -555,10 +701,11 @@ namespace Discord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name of the choice.</param>
|
/// <param name="name">The name of the choice.</param>
|
||||||
/// <param name="value">The value of the choice.</param>
|
/// <param name="value">The value of the choice.</param>
|
||||||
|
/// <param name="nameLocalizations">The localization dictionary to use for the name field of this command option choice.</param>
|
||||||
/// <returns>The current builder.</returns>
|
/// <returns>The current builder.</returns>
|
||||||
public SlashCommandOptionBuilder AddChoice(string name, float value)
|
public SlashCommandOptionBuilder AddChoice(string name, float value, IDictionary<string, string> nameLocalizations = null)
|
||||||
{
|
{
|
||||||
return AddChoiceInternal(name, value);
|
return AddChoiceInternal(name, value, nameLocalizations);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -566,13 +713,14 @@ namespace Discord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name of the choice.</param>
|
/// <param name="name">The name of the choice.</param>
|
||||||
/// <param name="value">The value of the choice.</param>
|
/// <param name="value">The value of the choice.</param>
|
||||||
|
/// <param name="nameLocalizations">The localization dictionary to use for the name field of this command option choice.</param>
|
||||||
/// <returns>The current builder.</returns>
|
/// <returns>The current builder.</returns>
|
||||||
public SlashCommandOptionBuilder AddChoice(string name, long value)
|
public SlashCommandOptionBuilder AddChoice(string name, long value, IDictionary<string, string> nameLocalizations = null)
|
||||||
{
|
{
|
||||||
return AddChoiceInternal(name, value);
|
return AddChoiceInternal(name, value, nameLocalizations);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SlashCommandOptionBuilder AddChoiceInternal(string name, object value)
|
private SlashCommandOptionBuilder AddChoiceInternal(string name, object value, IDictionary<string, string> nameLocalizations = null)
|
||||||
{
|
{
|
||||||
Choices ??= new List<ApplicationCommandOptionChoiceProperties>();
|
Choices ??= new List<ApplicationCommandOptionChoiceProperties>();
|
||||||
|
|
||||||
@@ -594,7 +742,8 @@ namespace Discord
|
|||||||
Choices.Add(new ApplicationCommandOptionChoiceProperties
|
Choices.Add(new ApplicationCommandOptionChoiceProperties
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Value = value
|
Value = value,
|
||||||
|
NameLocalizations = nameLocalizations
|
||||||
});
|
});
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
@@ -724,5 +873,107 @@ namespace Discord
|
|||||||
Type = type;
|
Type = type;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the <see cref="NameLocalizations"/> collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nameLocalizations">The localization dictionary to use for the name field of this command option.</param>
|
||||||
|
/// <returns>The current builder.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown if <paramref name="nameLocalizations"/> is null.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if any dictionary key is an invalid locale string.</exception>
|
||||||
|
public SlashCommandOptionBuilder WithNameLocalizations(IDictionary<string, string> nameLocalizations)
|
||||||
|
{
|
||||||
|
if (nameLocalizations is null)
|
||||||
|
throw new ArgumentNullException(nameof(nameLocalizations));
|
||||||
|
|
||||||
|
foreach (var (locale, name) in nameLocalizations)
|
||||||
|
{
|
||||||
|
if(!Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException($"Invalid locale: {locale}", nameof(locale));
|
||||||
|
|
||||||
|
EnsureValidCommandOptionName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
_nameLocalizations = new Dictionary<string, string>(nameLocalizations);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the <see cref="DescriptionLocalizations"/> collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="descriptionLocalizations">The localization dictionary to use for the description field of this command option.</param>
|
||||||
|
/// <returns>The current builder.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown if <paramref name="descriptionLocalizations"/> is null.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if any dictionary key is an invalid locale string.</exception>
|
||||||
|
public SlashCommandOptionBuilder WithDescriptionLocalizations(IDictionary<string, string> descriptionLocalizations)
|
||||||
|
{
|
||||||
|
if (descriptionLocalizations is null)
|
||||||
|
throw new ArgumentNullException(nameof(descriptionLocalizations));
|
||||||
|
|
||||||
|
foreach (var (locale, description) in _descriptionLocalizations)
|
||||||
|
{
|
||||||
|
if(!Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException($"Invalid locale: {locale}", nameof(locale));
|
||||||
|
|
||||||
|
EnsureValidCommandOptionDescription(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
_descriptionLocalizations = new Dictionary<string, string>(descriptionLocalizations);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new entry to the <see cref="NameLocalizations"/> collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="locale">Locale of the entry.</param>
|
||||||
|
/// <param name="name">Localized string for the name field.</param>
|
||||||
|
/// <returns>The current builder.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if <paramref name="locale"/> is an invalid locale string.</exception>
|
||||||
|
public SlashCommandOptionBuilder AddNameLocalization(string locale, string name)
|
||||||
|
{
|
||||||
|
if(!Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException($"Invalid locale: {locale}", nameof(locale));
|
||||||
|
|
||||||
|
EnsureValidCommandOptionName(name);
|
||||||
|
|
||||||
|
_descriptionLocalizations ??= new();
|
||||||
|
_nameLocalizations.Add(locale, name);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new entry to the <see cref="DescriptionLocalizations"/> collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="locale">Locale of the entry.</param>
|
||||||
|
/// <param name="description">Localized string for the description field.</param>
|
||||||
|
/// <returns>The current builder.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if <paramref name="locale"/> is an invalid locale string.</exception>
|
||||||
|
public SlashCommandOptionBuilder AddDescriptionLocalization(string locale, string description)
|
||||||
|
{
|
||||||
|
if(!Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException($"Invalid locale: {locale}", nameof(locale));
|
||||||
|
|
||||||
|
EnsureValidCommandOptionDescription(description);
|
||||||
|
|
||||||
|
_descriptionLocalizations ??= new();
|
||||||
|
_descriptionLocalizations.Add(locale, description);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureValidCommandOptionName(string name)
|
||||||
|
{
|
||||||
|
Preconditions.AtLeast(name.Length, 1, nameof(name));
|
||||||
|
Preconditions.AtMost(name.Length, SlashCommandBuilder.MaxNameLength, nameof(name));
|
||||||
|
if (!Regex.IsMatch(name, @"^[\w-]{1,32}$"))
|
||||||
|
throw new ArgumentException("Option name cannot contain any special characters or whitespaces!", nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureValidCommandOptionDescription(string description)
|
||||||
|
{
|
||||||
|
Preconditions.AtLeast(description.Length, 1, nameof(description));
|
||||||
|
Preconditions.AtMost(description.Length, SlashCommandBuilder.MaxDescriptionLength, nameof(description));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace System.Collections.Generic;
|
||||||
|
|
||||||
|
internal static class GenericCollectionExtensions
|
||||||
|
{
|
||||||
|
public static void Deconstruct<T1, T2>(this KeyValuePair<T1, T2> kvp, out T1 value1, out T2 value2)
|
||||||
|
{
|
||||||
|
value1 = kvp.Key;
|
||||||
|
value2 = kvp.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Dictionary<T1, T2> ToDictionary<T1, T2>(this IEnumerable<KeyValuePair<T1, T2>> kvp) =>
|
||||||
|
kvp.ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
}
|
||||||
@@ -155,12 +155,14 @@ namespace Discord
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a collection of all global commands.
|
/// Gets a collection of all global commands.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="withLocalizations">Whether to include full localization dictionaries in the returned objects, instead of the name localized and description localized fields.</param>
|
||||||
|
/// <param name="locale">The target locale of the localized name and description fields. Sets <c>X-Discord-Locale</c> header, which takes precedence over <c>Accept-Language</c>.</param>
|
||||||
/// <param name="options">The options to be used when sending the request.</param>
|
/// <param name="options">The options to be used when sending the request.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of global
|
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of global
|
||||||
/// application commands.
|
/// application commands.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
Task<IReadOnlyCollection<IApplicationCommand>> GetGlobalApplicationCommandsAsync(RequestOptions options = null);
|
Task<IReadOnlyCollection<IApplicationCommand>> GetGlobalApplicationCommandsAsync(bool withLocalizations = false, string locale = null, RequestOptions options = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a global application command.
|
/// Creates a global application command.
|
||||||
|
|||||||
@@ -30,9 +30,13 @@ namespace Discord.Net.Rest
|
|||||||
/// <param name="cancelToken">The cancellation token used to cancel the task.</param>
|
/// <param name="cancelToken">The cancellation token used to cancel the task.</param>
|
||||||
/// <param name="headerOnly">Indicates whether to send the header only.</param>
|
/// <param name="headerOnly">Indicates whether to send the header only.</param>
|
||||||
/// <param name="reason">The audit log reason.</param>
|
/// <param name="reason">The audit log reason.</param>
|
||||||
|
/// <param name="requestHeaders">Additional headers to be sent with the request.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<RestResponse> SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly = false, string reason = null);
|
Task<RestResponse> SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly = false, string reason = null,
|
||||||
Task<RestResponse> SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly = false, string reason = null);
|
IEnumerable<KeyValuePair<string, IEnumerable<string>>> requestHeaders = null);
|
||||||
Task<RestResponse> SendAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartParams, CancellationToken cancelToken, bool headerOnly = false, string reason = null);
|
Task<RestResponse> SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly = false, string reason = null,
|
||||||
|
IEnumerable<KeyValuePair<string, IEnumerable<string>>> requestHeaders = null);
|
||||||
|
Task<RestResponse> SendAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartParams, CancellationToken cancelToken, bool headerOnly = false, string reason = null,
|
||||||
|
IEnumerable<KeyValuePair<string, IEnumerable<string>>> requestHeaders = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Discord.Net;
|
using Discord.Net;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ namespace Discord
|
|||||||
/// Gets or sets the maximum time to wait for this request to complete.
|
/// Gets or sets the maximum time to wait for this request to complete.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Gets or set the max time, in milliseconds, to wait for this request to complete. If
|
/// Gets or set the max time, in milliseconds, to wait for this request to complete. If
|
||||||
/// <c>null</c>, a request will not time out. If a rate limit has been triggered for this request's bucket
|
/// <c>null</c>, a request will not time out. If a rate limit has been triggered for this request's bucket
|
||||||
/// and will not be unpaused in time, this request will fail immediately.
|
/// and will not be unpaused in time, this request will fail immediately.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
@@ -53,7 +54,7 @@ namespace Discord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This property can also be set in <see cref="DiscordConfig"/>.
|
/// This property can also be set in <see cref="DiscordConfig"/>.
|
||||||
/// On a per-request basis, the system clock should only be disabled
|
/// On a per-request basis, the system clock should only be disabled
|
||||||
/// when millisecond precision is especially important, and the
|
/// when millisecond precision is especially important, and the
|
||||||
/// hosting system is known to have a desynced clock.
|
/// hosting system is known to have a desynced clock.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
@@ -70,8 +71,10 @@ namespace Discord
|
|||||||
internal bool IsReactionBucket { get; set; }
|
internal bool IsReactionBucket { get; set; }
|
||||||
internal bool IsGatewayBucket { get; set; }
|
internal bool IsGatewayBucket { get; set; }
|
||||||
|
|
||||||
|
internal IDictionary<string, IEnumerable<string>> RequestHeaders { get; }
|
||||||
|
|
||||||
internal static RequestOptions CreateOrClone(RequestOptions options)
|
internal static RequestOptions CreateOrClone(RequestOptions options)
|
||||||
{
|
{
|
||||||
if (options == null)
|
if (options == null)
|
||||||
return new RequestOptions();
|
return new RequestOptions();
|
||||||
else
|
else
|
||||||
@@ -96,8 +99,9 @@ namespace Discord
|
|||||||
public RequestOptions()
|
public RequestOptions()
|
||||||
{
|
{
|
||||||
Timeout = DiscordConfig.DefaultRequestTimeout;
|
Timeout = DiscordConfig.DefaultRequestTimeout;
|
||||||
|
RequestHeaders = new Dictionary<string, IEnumerable<string>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public RequestOptions Clone() => MemberwiseClone() as RequestOptions;
|
public RequestOptions Clone() => MemberwiseClone() as RequestOptions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ namespace Discord
|
|||||||
if (obj.Value == null) throw CreateNotNullException(name, msg);
|
if (obj.Value == null) throw CreateNotNullException(name, msg);
|
||||||
if (obj.Value.Trim().Length == 0) throw CreateNotEmptyException(name, msg);
|
if (obj.Value.Trim().Length == 0) throw CreateNotEmptyException(name, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ArgumentException CreateNotEmptyException(string name, string msg)
|
private static ArgumentException CreateNotEmptyException(string name, string msg)
|
||||||
=> new ArgumentException(message: msg ?? "Argument cannot be blank.", paramName: name);
|
=> new ArgumentException(message: msg ?? "Argument cannot be blank.", paramName: name);
|
||||||
@@ -129,7 +129,7 @@ namespace Discord
|
|||||||
|
|
||||||
private static ArgumentException CreateNotEqualException<T>(string name, string msg, T value)
|
private static ArgumentException CreateNotEqualException<T>(string name, string msg, T value)
|
||||||
=> new ArgumentException(message: msg ?? $"Value may not be equal to {value}.", paramName: name);
|
=> new ArgumentException(message: msg ?? $"Value may not be equal to {value}.", paramName: name);
|
||||||
|
|
||||||
/// <exception cref="ArgumentException">Value must be at least <paramref name="value"/>.</exception>
|
/// <exception cref="ArgumentException">Value must be at least <paramref name="value"/>.</exception>
|
||||||
public static void AtLeast(sbyte obj, sbyte value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); }
|
public static void AtLeast(sbyte obj, sbyte value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); }
|
||||||
/// <exception cref="ArgumentException">Value must be at least <paramref name="value"/>.</exception>
|
/// <exception cref="ArgumentException">Value must be at least <paramref name="value"/>.</exception>
|
||||||
@@ -165,7 +165,7 @@ namespace Discord
|
|||||||
|
|
||||||
private static ArgumentException CreateAtLeastException<T>(string name, string msg, T value)
|
private static ArgumentException CreateAtLeastException<T>(string name, string msg, T value)
|
||||||
=> new ArgumentException(message: msg ?? $"Value must be at least {value}.", paramName: name);
|
=> new ArgumentException(message: msg ?? $"Value must be at least {value}.", paramName: name);
|
||||||
|
|
||||||
/// <exception cref="ArgumentException">Value must be greater than <paramref name="value"/>.</exception>
|
/// <exception cref="ArgumentException">Value must be greater than <paramref name="value"/>.</exception>
|
||||||
public static void GreaterThan(sbyte obj, sbyte value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); }
|
public static void GreaterThan(sbyte obj, sbyte value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); }
|
||||||
/// <exception cref="ArgumentException">Value must be greater than <paramref name="value"/>.</exception>
|
/// <exception cref="ArgumentException">Value must be greater than <paramref name="value"/>.</exception>
|
||||||
@@ -201,7 +201,7 @@ namespace Discord
|
|||||||
|
|
||||||
private static ArgumentException CreateGreaterThanException<T>(string name, string msg, T value)
|
private static ArgumentException CreateGreaterThanException<T>(string name, string msg, T value)
|
||||||
=> new ArgumentException(message: msg ?? $"Value must be greater than {value}.", paramName: name);
|
=> new ArgumentException(message: msg ?? $"Value must be greater than {value}.", paramName: name);
|
||||||
|
|
||||||
/// <exception cref="ArgumentException">Value must be at most <paramref name="value"/>.</exception>
|
/// <exception cref="ArgumentException">Value must be at most <paramref name="value"/>.</exception>
|
||||||
public static void AtMost(sbyte obj, sbyte value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); }
|
public static void AtMost(sbyte obj, sbyte value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); }
|
||||||
/// <exception cref="ArgumentException">Value must be at most <paramref name="value"/>.</exception>
|
/// <exception cref="ArgumentException">Value must be at most <paramref name="value"/>.</exception>
|
||||||
@@ -237,7 +237,7 @@ namespace Discord
|
|||||||
|
|
||||||
private static ArgumentException CreateAtMostException<T>(string name, string msg, T value)
|
private static ArgumentException CreateAtMostException<T>(string name, string msg, T value)
|
||||||
=> new ArgumentException(message: msg ?? $"Value must be at most {value}.", paramName: name);
|
=> new ArgumentException(message: msg ?? $"Value must be at most {value}.", paramName: name);
|
||||||
|
|
||||||
/// <exception cref="ArgumentException">Value must be less than <paramref name="value"/>.</exception>
|
/// <exception cref="ArgumentException">Value must be less than <paramref name="value"/>.</exception>
|
||||||
public static void LessThan(sbyte obj, sbyte value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); }
|
public static void LessThan(sbyte obj, sbyte value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); }
|
||||||
/// <exception cref="ArgumentException">Value must be less than <paramref name="value"/>.</exception>
|
/// <exception cref="ArgumentException">Value must be less than <paramref name="value"/>.</exception>
|
||||||
|
|||||||
@@ -83,6 +83,11 @@ namespace Discord.Interactions
|
|||||||
public event Func<ModalCommandInfo, IInteractionContext, IResult, Task> ModalCommandExecuted { add { _modalCommandExecutedEvent.Add(value); } remove { _modalCommandExecutedEvent.Remove(value); } }
|
public event Func<ModalCommandInfo, IInteractionContext, IResult, Task> ModalCommandExecuted { add { _modalCommandExecutedEvent.Add(value); } remove { _modalCommandExecutedEvent.Remove(value); } }
|
||||||
internal readonly AsyncEvent<Func<ModalCommandInfo, IInteractionContext, IResult, Task>> _modalCommandExecutedEvent = new();
|
internal readonly AsyncEvent<Func<ModalCommandInfo, IInteractionContext, IResult, Task>> _modalCommandExecutedEvent = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the <see cref="ILocalizationManager"/> used by this Interaction Service instance to localize strings.
|
||||||
|
/// </summary>
|
||||||
|
public ILocalizationManager LocalizationManager { get; set; }
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<Type, ModuleInfo> _typedModuleDefs;
|
private readonly ConcurrentDictionary<Type, ModuleInfo> _typedModuleDefs;
|
||||||
private readonly CommandMap<SlashCommandInfo> _slashCommandMap;
|
private readonly CommandMap<SlashCommandInfo> _slashCommandMap;
|
||||||
private readonly ConcurrentDictionary<ApplicationCommandType, CommandMap<ContextCommandInfo>> _contextCommandMaps;
|
private readonly ConcurrentDictionary<ApplicationCommandType, CommandMap<ContextCommandInfo>> _contextCommandMaps;
|
||||||
@@ -203,6 +208,7 @@ namespace Discord.Interactions
|
|||||||
_enableAutocompleteHandlers = config.EnableAutocompleteHandlers;
|
_enableAutocompleteHandlers = config.EnableAutocompleteHandlers;
|
||||||
_autoServiceScopes = config.AutoServiceScopes;
|
_autoServiceScopes = config.AutoServiceScopes;
|
||||||
_restResponseCallback = config.RestResponseCallback;
|
_restResponseCallback = config.RestResponseCallback;
|
||||||
|
LocalizationManager = config.LocalizationManager;
|
||||||
|
|
||||||
_typeConverterMap = new TypeMap<TypeConverter, IApplicationCommandInteractionDataOption>(this, new ConcurrentDictionary<Type, TypeConverter>
|
_typeConverterMap = new TypeMap<TypeConverter, IApplicationCommandInteractionDataOption>(this, new ConcurrentDictionary<Type, TypeConverter>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ namespace Discord.Interactions
|
|||||||
/// Gets or sets whether a command execution should exit when a modal command encounters a missing modal component value.
|
/// Gets or sets whether a command execution should exit when a modal command encounters a missing modal component value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ExitOnMissingModalField { get; set; } = false;
|
public bool ExitOnMissingModalField { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Localization provider to be used when registering application commands.
|
||||||
|
/// </summary>
|
||||||
|
public ILocalizationManager LocalizationManager { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Discord.Interactions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Respresents a localization provider for Discord Application Commands.
|
||||||
|
/// </summary>
|
||||||
|
public interface ILocalizationManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get every the resource name for every available locale.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">Location of the resource.</param>
|
||||||
|
/// <param name="destinationType">Type of the resource.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A dictionary containing every available locale and the resource name.
|
||||||
|
/// </returns>
|
||||||
|
IDictionary<string, string> GetAllNames(IList<string> key, LocalizationTarget destinationType);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get every the resource description for every available locale.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">Location of the resource.</param>
|
||||||
|
/// <param name="destinationType">Type of the resource.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A dictionary containing every available locale and the resource name.
|
||||||
|
/// </returns>
|
||||||
|
IDictionary<string, string> GetAllDescriptions(IList<string> key, LocalizationTarget destinationType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Discord.Interactions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The default localization provider for Json resource files.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class JsonLocalizationManager : ILocalizationManager
|
||||||
|
{
|
||||||
|
private const string NameIdentifier = "name";
|
||||||
|
private const string DescriptionIdentifier = "description";
|
||||||
|
private const string SpaceToken = "~";
|
||||||
|
|
||||||
|
private readonly string _basePath;
|
||||||
|
private readonly string _fileName;
|
||||||
|
private readonly Regex _localeParserRegex = new Regex(@"\w+.(?<locale>\w{2}(?:-\w{2})?).json", RegexOptions.Compiled | RegexOptions.Singleline);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JsonLocalizationManager"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="basePath">Base path of the Json file.</param>
|
||||||
|
/// <param name="fileName">Name of the Json file.</param>
|
||||||
|
public JsonLocalizationManager(string basePath, string fileName)
|
||||||
|
{
|
||||||
|
_basePath = basePath;
|
||||||
|
_fileName = fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IDictionary<string, string> GetAllDescriptions(IList<string> key, LocalizationTarget destinationType) =>
|
||||||
|
GetValues(key, DescriptionIdentifier);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IDictionary<string, string> GetAllNames(IList<string> key, LocalizationTarget destinationType) =>
|
||||||
|
GetValues(key, NameIdentifier);
|
||||||
|
|
||||||
|
private string[] GetAllFiles() =>
|
||||||
|
Directory.GetFiles(_basePath, $"{_fileName}.*.json", SearchOption.TopDirectoryOnly);
|
||||||
|
|
||||||
|
private IDictionary<string, string> GetValues(IList<string> key, string identifier)
|
||||||
|
{
|
||||||
|
var result = new Dictionary<string, string>();
|
||||||
|
var files = GetAllFiles();
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
var match = _localeParserRegex.Match(Path.GetFileName(file));
|
||||||
|
if (!match.Success)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var locale = match.Groups["locale"].Value;
|
||||||
|
|
||||||
|
using var sr = new StreamReader(file);
|
||||||
|
using var jr = new JsonTextReader(sr);
|
||||||
|
var obj = JObject.Load(jr);
|
||||||
|
var token = string.Join(".", key.Select(x => $"['{x}']")) + $".{identifier}";
|
||||||
|
var value = (string)obj.SelectToken(token);
|
||||||
|
if (value is not null)
|
||||||
|
result[locale] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Resources;
|
||||||
|
|
||||||
|
namespace Discord.Interactions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The default localization provider for Resx files.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ResxLocalizationManager : ILocalizationManager
|
||||||
|
{
|
||||||
|
private const string NameIdentifier = "name";
|
||||||
|
private const string DescriptionIdentifier = "description";
|
||||||
|
|
||||||
|
private readonly ResourceManager _resourceManager;
|
||||||
|
private readonly IEnumerable<CultureInfo> _supportedLocales;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ResxLocalizationManager"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseResource">Name of the base resource.</param>
|
||||||
|
/// <param name="assembly">The main assembly for the resources.</param>
|
||||||
|
/// <param name="supportedLocales">Cultures the <see cref="ResxLocalizationManager"/> should search for.</param>
|
||||||
|
public ResxLocalizationManager(string baseResource, Assembly assembly, params CultureInfo[] supportedLocales)
|
||||||
|
{
|
||||||
|
_supportedLocales = supportedLocales;
|
||||||
|
_resourceManager = new ResourceManager(baseResource, assembly);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IDictionary<string, string> GetAllDescriptions(IList<string> key, LocalizationTarget destinationType) =>
|
||||||
|
GetValues(key, DescriptionIdentifier);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IDictionary<string, string> GetAllNames(IList<string> key, LocalizationTarget destinationType) =>
|
||||||
|
GetValues(key, NameIdentifier);
|
||||||
|
|
||||||
|
private IDictionary<string, string> GetValues(IList<string> key, string identifier)
|
||||||
|
{
|
||||||
|
var entryKey = (string.Join(".", key) + "." + identifier);
|
||||||
|
|
||||||
|
var result = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
foreach (var locale in _supportedLocales)
|
||||||
|
{
|
||||||
|
var value = _resourceManager.GetString(entryKey, locale);
|
||||||
|
if (value is not null)
|
||||||
|
result[locale.Name] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/Discord.Net.Interactions/LocalizationTarget.cs
Normal file
25
src/Discord.Net.Interactions/LocalizationTarget.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
namespace Discord.Interactions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Resource targets for localization.
|
||||||
|
/// </summary>
|
||||||
|
public enum LocalizationTarget
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Target is a <see cref="IInteractionModuleBase"/> tagged with a <see cref="GroupAttribute"/>.
|
||||||
|
/// </summary>
|
||||||
|
Group,
|
||||||
|
/// <summary>
|
||||||
|
/// Target is an application command method.
|
||||||
|
/// </summary>
|
||||||
|
Command,
|
||||||
|
/// <summary>
|
||||||
|
/// Target is a Slash Command parameter.
|
||||||
|
/// </summary>
|
||||||
|
Parameter,
|
||||||
|
/// <summary>
|
||||||
|
/// Target is a Slash Command parameter choice.
|
||||||
|
/// </summary>
|
||||||
|
Choice
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,7 +42,7 @@ namespace Discord.Interactions
|
|||||||
|
|
||||||
public void RemoveCommand(T command)
|
public void RemoveCommand(T command)
|
||||||
{
|
{
|
||||||
var key = ParseCommandName(command);
|
var key = CommandHierarchy.GetCommandPath(command);
|
||||||
|
|
||||||
_root.RemoveCommand(key, 0);
|
_root.RemoveCommand(key, 0);
|
||||||
}
|
}
|
||||||
@@ -60,28 +60,9 @@ namespace Discord.Interactions
|
|||||||
|
|
||||||
private void AddCommand(T command)
|
private void AddCommand(T command)
|
||||||
{
|
{
|
||||||
var key = ParseCommandName(command);
|
var key = CommandHierarchy.GetCommandPath(command);
|
||||||
|
|
||||||
_root.AddCommand(key, 0, command);
|
_root.AddCommand(key, 0, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IList<string> ParseCommandName(T command)
|
|
||||||
{
|
|
||||||
var keywords = new List<string>() { command.Name };
|
|
||||||
|
|
||||||
var currentParent = command.Module;
|
|
||||||
|
|
||||||
while (currentParent != null)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(currentParent.SlashGroupName))
|
|
||||||
keywords.Add(currentParent.SlashGroupName);
|
|
||||||
|
|
||||||
currentParent = currentParent.Parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
keywords.Reverse();
|
|
||||||
|
|
||||||
return keywords;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Discord.Interactions
|
namespace Discord.Interactions
|
||||||
@@ -9,6 +10,9 @@ namespace Discord.Interactions
|
|||||||
#region Parameters
|
#region Parameters
|
||||||
public static ApplicationCommandOptionProperties ToApplicationCommandOptionProps(this SlashCommandParameterInfo parameterInfo)
|
public static ApplicationCommandOptionProperties ToApplicationCommandOptionProps(this SlashCommandParameterInfo parameterInfo)
|
||||||
{
|
{
|
||||||
|
var localizationManager = parameterInfo.Command.Module.CommandService.LocalizationManager;
|
||||||
|
var parameterPath = parameterInfo.GetParameterPath();
|
||||||
|
|
||||||
var props = new ApplicationCommandOptionProperties
|
var props = new ApplicationCommandOptionProperties
|
||||||
{
|
{
|
||||||
Name = parameterInfo.Name,
|
Name = parameterInfo.Name,
|
||||||
@@ -18,12 +22,15 @@ namespace Discord.Interactions
|
|||||||
Choices = parameterInfo.Choices?.Select(x => new ApplicationCommandOptionChoiceProperties
|
Choices = parameterInfo.Choices?.Select(x => new ApplicationCommandOptionChoiceProperties
|
||||||
{
|
{
|
||||||
Name = x.Name,
|
Name = x.Name,
|
||||||
Value = x.Value
|
Value = x.Value,
|
||||||
|
NameLocalizations = localizationManager?.GetAllNames(parameterInfo.GetChoicePath(x), LocalizationTarget.Choice) ?? ImmutableDictionary<string, string>.Empty
|
||||||
})?.ToList(),
|
})?.ToList(),
|
||||||
ChannelTypes = parameterInfo.ChannelTypes?.ToList(),
|
ChannelTypes = parameterInfo.ChannelTypes?.ToList(),
|
||||||
IsAutocomplete = parameterInfo.IsAutocomplete,
|
IsAutocomplete = parameterInfo.IsAutocomplete,
|
||||||
MaxValue = parameterInfo.MaxValue,
|
MaxValue = parameterInfo.MaxValue,
|
||||||
MinValue = parameterInfo.MinValue,
|
MinValue = parameterInfo.MinValue,
|
||||||
|
NameLocalizations = localizationManager?.GetAllNames(parameterPath, LocalizationTarget.Parameter) ?? ImmutableDictionary<string, string>.Empty,
|
||||||
|
DescriptionLocalizations = localizationManager?.GetAllDescriptions(parameterPath, LocalizationTarget.Parameter) ?? ImmutableDictionary<string, string>.Empty,
|
||||||
MinLength = parameterInfo.MinLength,
|
MinLength = parameterInfo.MinLength,
|
||||||
MaxLength = parameterInfo.MaxLength,
|
MaxLength = parameterInfo.MaxLength,
|
||||||
};
|
};
|
||||||
@@ -38,13 +45,19 @@ namespace Discord.Interactions
|
|||||||
|
|
||||||
public static SlashCommandProperties ToApplicationCommandProps(this SlashCommandInfo commandInfo)
|
public static SlashCommandProperties ToApplicationCommandProps(this SlashCommandInfo commandInfo)
|
||||||
{
|
{
|
||||||
|
var commandPath = commandInfo.GetCommandPath();
|
||||||
|
var localizationManager = commandInfo.Module.CommandService.LocalizationManager;
|
||||||
|
|
||||||
var props = new SlashCommandBuilder()
|
var props = new SlashCommandBuilder()
|
||||||
{
|
{
|
||||||
Name = commandInfo.Name,
|
Name = commandInfo.Name,
|
||||||
Description = commandInfo.Description,
|
Description = commandInfo.Description,
|
||||||
|
IsDefaultPermission = commandInfo.DefaultPermission,
|
||||||
IsDMEnabled = commandInfo.IsEnabledInDm,
|
IsDMEnabled = commandInfo.IsEnabledInDm,
|
||||||
DefaultMemberPermissions = ((commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0)).SanitizeGuildPermissions(),
|
DefaultMemberPermissions = ((commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0)).SanitizeGuildPermissions(),
|
||||||
}.Build();
|
}.WithNameLocalizations(localizationManager?.GetAllNames(commandPath, LocalizationTarget.Command) ?? ImmutableDictionary<string, string>.Empty)
|
||||||
|
.WithDescriptionLocalizations(localizationManager?.GetAllDescriptions(commandPath, LocalizationTarget.Command) ?? ImmutableDictionary<string, string>.Empty)
|
||||||
|
.Build();
|
||||||
|
|
||||||
if (commandInfo.Parameters.Count > SlashCommandBuilder.MaxOptionsCount)
|
if (commandInfo.Parameters.Count > SlashCommandBuilder.MaxOptionsCount)
|
||||||
throw new InvalidOperationException($"Slash Commands cannot have more than {SlashCommandBuilder.MaxOptionsCount} command parameters");
|
throw new InvalidOperationException($"Slash Commands cannot have more than {SlashCommandBuilder.MaxOptionsCount} command parameters");
|
||||||
@@ -54,18 +67,30 @@ namespace Discord.Interactions
|
|||||||
return props;
|
return props;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ApplicationCommandOptionProperties ToApplicationCommandOptionProps(this SlashCommandInfo commandInfo) =>
|
public static ApplicationCommandOptionProperties ToApplicationCommandOptionProps(this SlashCommandInfo commandInfo)
|
||||||
new ApplicationCommandOptionProperties
|
{
|
||||||
|
var localizationManager = commandInfo.Module.CommandService.LocalizationManager;
|
||||||
|
var commandPath = commandInfo.GetCommandPath();
|
||||||
|
|
||||||
|
return new ApplicationCommandOptionProperties
|
||||||
{
|
{
|
||||||
Name = commandInfo.Name,
|
Name = commandInfo.Name,
|
||||||
Description = commandInfo.Description,
|
Description = commandInfo.Description,
|
||||||
Type = ApplicationCommandOptionType.SubCommand,
|
Type = ApplicationCommandOptionType.SubCommand,
|
||||||
IsRequired = false,
|
IsRequired = false,
|
||||||
Options = commandInfo.FlattenedParameters?.Select(x => x.ToApplicationCommandOptionProps())?.ToList()
|
Options = commandInfo.FlattenedParameters?.Select(x => x.ToApplicationCommandOptionProps())
|
||||||
|
?.ToList(),
|
||||||
|
NameLocalizations = localizationManager?.GetAllNames(commandPath, LocalizationTarget.Command) ?? ImmutableDictionary<string, string>.Empty,
|
||||||
|
DescriptionLocalizations = localizationManager?.GetAllDescriptions(commandPath, LocalizationTarget.Command) ?? ImmutableDictionary<string, string>.Empty
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static ApplicationCommandProperties ToApplicationCommandProps(this ContextCommandInfo commandInfo)
|
public static ApplicationCommandProperties ToApplicationCommandProps(this ContextCommandInfo commandInfo)
|
||||||
=> commandInfo.CommandType switch
|
{
|
||||||
|
var localizationManager = commandInfo.Module.CommandService.LocalizationManager;
|
||||||
|
var commandPath = commandInfo.GetCommandPath();
|
||||||
|
|
||||||
|
return commandInfo.CommandType switch
|
||||||
{
|
{
|
||||||
ApplicationCommandType.Message => new MessageCommandBuilder
|
ApplicationCommandType.Message => new MessageCommandBuilder
|
||||||
{
|
{
|
||||||
@@ -73,16 +98,21 @@ namespace Discord.Interactions
|
|||||||
IsDefaultPermission = commandInfo.DefaultPermission,
|
IsDefaultPermission = commandInfo.DefaultPermission,
|
||||||
DefaultMemberPermissions = ((commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0)).SanitizeGuildPermissions(),
|
DefaultMemberPermissions = ((commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0)).SanitizeGuildPermissions(),
|
||||||
IsDMEnabled = commandInfo.IsEnabledInDm
|
IsDMEnabled = commandInfo.IsEnabledInDm
|
||||||
}.Build(),
|
}
|
||||||
|
.WithNameLocalizations(localizationManager?.GetAllNames(commandPath, LocalizationTarget.Command) ?? ImmutableDictionary<string, string>.Empty)
|
||||||
|
.Build(),
|
||||||
ApplicationCommandType.User => new UserCommandBuilder
|
ApplicationCommandType.User => new UserCommandBuilder
|
||||||
{
|
{
|
||||||
Name = commandInfo.Name,
|
Name = commandInfo.Name,
|
||||||
IsDefaultPermission = commandInfo.DefaultPermission,
|
IsDefaultPermission = commandInfo.DefaultPermission,
|
||||||
DefaultMemberPermissions = ((commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0)).SanitizeGuildPermissions(),
|
DefaultMemberPermissions = ((commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0)).SanitizeGuildPermissions(),
|
||||||
IsDMEnabled = commandInfo.IsEnabledInDm
|
IsDMEnabled = commandInfo.IsEnabledInDm
|
||||||
}.Build(),
|
}
|
||||||
|
.WithNameLocalizations(localizationManager?.GetAllNames(commandPath, LocalizationTarget.Command) ?? ImmutableDictionary<string, string>.Empty)
|
||||||
|
.Build(),
|
||||||
_ => throw new InvalidOperationException($"{commandInfo.CommandType} isn't a supported command type.")
|
_ => throw new InvalidOperationException($"{commandInfo.CommandType} isn't a supported command type.")
|
||||||
};
|
};
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Modules
|
#region Modules
|
||||||
@@ -123,6 +153,9 @@ namespace Discord.Interactions
|
|||||||
|
|
||||||
options.AddRange(moduleInfo.SubModules?.SelectMany(x => x.ParseSubModule(args, ignoreDontRegister)));
|
options.AddRange(moduleInfo.SubModules?.SelectMany(x => x.ParseSubModule(args, ignoreDontRegister)));
|
||||||
|
|
||||||
|
var localizationManager = moduleInfo.CommandService.LocalizationManager;
|
||||||
|
var modulePath = moduleInfo.GetModulePath();
|
||||||
|
|
||||||
var props = new SlashCommandBuilder
|
var props = new SlashCommandBuilder
|
||||||
{
|
{
|
||||||
Name = moduleInfo.SlashGroupName,
|
Name = moduleInfo.SlashGroupName,
|
||||||
@@ -130,7 +163,10 @@ namespace Discord.Interactions
|
|||||||
IsDefaultPermission = moduleInfo.DefaultPermission,
|
IsDefaultPermission = moduleInfo.DefaultPermission,
|
||||||
IsDMEnabled = moduleInfo.IsEnabledInDm,
|
IsDMEnabled = moduleInfo.IsEnabledInDm,
|
||||||
DefaultMemberPermissions = moduleInfo.DefaultMemberPermissions
|
DefaultMemberPermissions = moduleInfo.DefaultMemberPermissions
|
||||||
}.Build();
|
}
|
||||||
|
.WithNameLocalizations(localizationManager?.GetAllNames(modulePath, LocalizationTarget.Group) ?? ImmutableDictionary<string, string>.Empty)
|
||||||
|
.WithDescriptionLocalizations(localizationManager?.GetAllDescriptions(modulePath, LocalizationTarget.Group) ?? ImmutableDictionary<string, string>.Empty)
|
||||||
|
.Build();
|
||||||
|
|
||||||
if (options.Count > SlashCommandBuilder.MaxOptionsCount)
|
if (options.Count > SlashCommandBuilder.MaxOptionsCount)
|
||||||
throw new InvalidOperationException($"Slash Commands cannot have more than {SlashCommandBuilder.MaxOptionsCount} command parameters");
|
throw new InvalidOperationException($"Slash Commands cannot have more than {SlashCommandBuilder.MaxOptionsCount} command parameters");
|
||||||
@@ -168,7 +204,11 @@ namespace Discord.Interactions
|
|||||||
Name = moduleInfo.SlashGroupName,
|
Name = moduleInfo.SlashGroupName,
|
||||||
Description = moduleInfo.Description,
|
Description = moduleInfo.Description,
|
||||||
Type = ApplicationCommandOptionType.SubCommandGroup,
|
Type = ApplicationCommandOptionType.SubCommandGroup,
|
||||||
Options = options
|
Options = options,
|
||||||
|
NameLocalizations = moduleInfo.CommandService.LocalizationManager?.GetAllNames(moduleInfo.GetModulePath(), LocalizationTarget.Group)
|
||||||
|
?? ImmutableDictionary<string, string>.Empty,
|
||||||
|
DescriptionLocalizations = moduleInfo.CommandService.LocalizationManager?.GetAllDescriptions(moduleInfo.GetModulePath(), LocalizationTarget.Group)
|
||||||
|
?? ImmutableDictionary<string, string>.Empty,
|
||||||
} };
|
} };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,17 +223,29 @@ namespace Discord.Interactions
|
|||||||
Name = command.Name,
|
Name = command.Name,
|
||||||
Description = command.Description,
|
Description = command.Description,
|
||||||
IsDefaultPermission = command.IsDefaultPermission,
|
IsDefaultPermission = command.IsDefaultPermission,
|
||||||
Options = command.Options?.Select(x => x.ToApplicationCommandOptionProps())?.ToList() ?? Optional<List<ApplicationCommandOptionProperties>>.Unspecified
|
DefaultMemberPermissions = (GuildPermission)command.DefaultMemberPermissions.RawValue,
|
||||||
|
IsDMEnabled = command.IsEnabledInDm,
|
||||||
|
Options = command.Options?.Select(x => x.ToApplicationCommandOptionProps())?.ToList() ?? Optional<List<ApplicationCommandOptionProperties>>.Unspecified,
|
||||||
|
NameLocalizations = command.NameLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary<string, string>.Empty,
|
||||||
|
DescriptionLocalizations = command.DescriptionLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary<string, string>.Empty,
|
||||||
},
|
},
|
||||||
ApplicationCommandType.User => new UserCommandProperties
|
ApplicationCommandType.User => new UserCommandProperties
|
||||||
{
|
{
|
||||||
Name = command.Name,
|
Name = command.Name,
|
||||||
IsDefaultPermission = command.IsDefaultPermission
|
IsDefaultPermission = command.IsDefaultPermission,
|
||||||
|
DefaultMemberPermissions = (GuildPermission)command.DefaultMemberPermissions.RawValue,
|
||||||
|
IsDMEnabled = command.IsEnabledInDm,
|
||||||
|
NameLocalizations = command.NameLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary<string, string>.Empty,
|
||||||
|
DescriptionLocalizations = command.DescriptionLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary<string, string>.Empty
|
||||||
},
|
},
|
||||||
ApplicationCommandType.Message => new MessageCommandProperties
|
ApplicationCommandType.Message => new MessageCommandProperties
|
||||||
{
|
{
|
||||||
Name = command.Name,
|
Name = command.Name,
|
||||||
IsDefaultPermission = command.IsDefaultPermission
|
IsDefaultPermission = command.IsDefaultPermission,
|
||||||
|
DefaultMemberPermissions = (GuildPermission)command.DefaultMemberPermissions.RawValue,
|
||||||
|
IsDMEnabled = command.IsEnabledInDm,
|
||||||
|
NameLocalizations = command.NameLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary<string, string>.Empty,
|
||||||
|
DescriptionLocalizations = command.DescriptionLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary<string, string>.Empty
|
||||||
},
|
},
|
||||||
_ => throw new InvalidOperationException($"Cannot create command properties for command type {command.Type}"),
|
_ => throw new InvalidOperationException($"Cannot create command properties for command type {command.Type}"),
|
||||||
};
|
};
|
||||||
@@ -206,18 +258,20 @@ namespace Discord.Interactions
|
|||||||
Description = commandOption.Description,
|
Description = commandOption.Description,
|
||||||
Type = commandOption.Type,
|
Type = commandOption.Type,
|
||||||
IsRequired = commandOption.IsRequired,
|
IsRequired = commandOption.IsRequired,
|
||||||
|
ChannelTypes = commandOption.ChannelTypes?.ToList(),
|
||||||
|
IsAutocomplete = commandOption.IsAutocomplete.GetValueOrDefault(),
|
||||||
|
MinValue = commandOption.MinValue,
|
||||||
|
MaxValue = commandOption.MaxValue,
|
||||||
Choices = commandOption.Choices?.Select(x => new ApplicationCommandOptionChoiceProperties
|
Choices = commandOption.Choices?.Select(x => new ApplicationCommandOptionChoiceProperties
|
||||||
{
|
{
|
||||||
Name = x.Name,
|
Name = x.Name,
|
||||||
Value = x.Value
|
Value = x.Value
|
||||||
}).ToList(),
|
}).ToList(),
|
||||||
Options = commandOption.Options?.Select(x => x.ToApplicationCommandOptionProps()).ToList(),
|
Options = commandOption.Options?.Select(x => x.ToApplicationCommandOptionProps()).ToList(),
|
||||||
|
NameLocalizations = commandOption.NameLocalizations?.ToImmutableDictionary(),
|
||||||
|
DescriptionLocalizations = commandOption.DescriptionLocalizations?.ToImmutableDictionary(),
|
||||||
MaxLength = commandOption.MaxLength,
|
MaxLength = commandOption.MaxLength,
|
||||||
MinLength = commandOption.MinLength,
|
MinLength = commandOption.MinLength,
|
||||||
MaxValue = commandOption.MaxValue,
|
|
||||||
MinValue = commandOption.MinValue,
|
|
||||||
IsAutocomplete = commandOption.IsAutocomplete.GetValueOrDefault(),
|
|
||||||
ChannelTypes = commandOption.ChannelTypes.ToList(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static Modal ToModal(this ModalInfo modalInfo, string customId, Action<ModalBuilder> modifyModal = null)
|
public static Modal ToModal(this ModalInfo modalInfo, string customId, Action<ModalBuilder> modifyModal = null)
|
||||||
|
|||||||
53
src/Discord.Net.Interactions/Utilities/CommandHierarchy.cs
Normal file
53
src/Discord.Net.Interactions/Utilities/CommandHierarchy.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Discord.Interactions
|
||||||
|
{
|
||||||
|
internal static class CommandHierarchy
|
||||||
|
{
|
||||||
|
public const char EscapeChar = '$';
|
||||||
|
|
||||||
|
public static IList<string> GetModulePath(this ModuleInfo moduleInfo)
|
||||||
|
{
|
||||||
|
var result = new List<string>();
|
||||||
|
|
||||||
|
var current = moduleInfo;
|
||||||
|
while (current is not null)
|
||||||
|
{
|
||||||
|
if (current.IsSlashGroup)
|
||||||
|
result.Insert(0, current.SlashGroupName);
|
||||||
|
|
||||||
|
current = current.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IList<string> GetCommandPath(this ICommandInfo commandInfo)
|
||||||
|
{
|
||||||
|
if (commandInfo.IgnoreGroupNames)
|
||||||
|
return new string[] { commandInfo.Name };
|
||||||
|
|
||||||
|
var path = commandInfo.Module.GetModulePath();
|
||||||
|
path.Add(commandInfo.Name);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IList<string> GetParameterPath(this IParameterInfo parameterInfo)
|
||||||
|
{
|
||||||
|
var path = parameterInfo.Command.GetCommandPath();
|
||||||
|
path.Add(parameterInfo.Name);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IList<string> GetChoicePath(this IParameterInfo parameterInfo, ParameterChoice choice)
|
||||||
|
{
|
||||||
|
var path = parameterInfo.GetParameterPath();
|
||||||
|
path.Add(choice.Name);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IList<string> GetTypePath(Type type) =>
|
||||||
|
new string[] { EscapeChar + type.FullName };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Discord.API
|
namespace Discord.API
|
||||||
{
|
{
|
||||||
@@ -25,6 +26,18 @@ namespace Discord.API
|
|||||||
[JsonProperty("default_permission")]
|
[JsonProperty("default_permission")]
|
||||||
public Optional<bool> DefaultPermissions { get; set; }
|
public Optional<bool> DefaultPermissions { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("name_localizations")]
|
||||||
|
public Optional<Dictionary<string, string>> NameLocalizations { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("description_localizations")]
|
||||||
|
public Optional<Dictionary<string, string>> DescriptionLocalizations { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("name_localized")]
|
||||||
|
public Optional<string> NameLocalized { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("description_localized")]
|
||||||
|
public Optional<string> DescriptionLocalized { get; set; }
|
||||||
|
|
||||||
// V2 Permissions
|
// V2 Permissions
|
||||||
[JsonProperty("dm_permission")]
|
[JsonProperty("dm_permission")]
|
||||||
public Optional<bool?> DmPermission { get; set; }
|
public Optional<bool?> DmPermission { get; set; }
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Discord.API
|
namespace Discord.API
|
||||||
@@ -38,6 +39,18 @@ namespace Discord.API
|
|||||||
[JsonProperty("channel_types")]
|
[JsonProperty("channel_types")]
|
||||||
public Optional<ChannelType[]> ChannelTypes { get; set; }
|
public Optional<ChannelType[]> ChannelTypes { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("name_localizations")]
|
||||||
|
public Optional<Dictionary<string, string>> NameLocalizations { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("description_localizations")]
|
||||||
|
public Optional<Dictionary<string, string>> DescriptionLocalizations { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("name_localized")]
|
||||||
|
public Optional<string> NameLocalized { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("description_localized")]
|
||||||
|
public Optional<string> DescriptionLocalized { get; set; }
|
||||||
|
|
||||||
[JsonProperty("min_length")]
|
[JsonProperty("min_length")]
|
||||||
public Optional<int> MinLength { get; set; }
|
public Optional<int> MinLength { get; set; }
|
||||||
|
|
||||||
@@ -69,6 +82,11 @@ namespace Discord.API
|
|||||||
Name = cmd.Name;
|
Name = cmd.Name;
|
||||||
Type = cmd.Type;
|
Type = cmd.Type;
|
||||||
Description = cmd.Description;
|
Description = cmd.Description;
|
||||||
|
|
||||||
|
NameLocalizations = cmd.NameLocalizations?.ToDictionary() ?? Optional<Dictionary<string, string>>.Unspecified;
|
||||||
|
DescriptionLocalizations = cmd.DescriptionLocalizations?.ToDictionary() ?? Optional<Dictionary<string, string>>.Unspecified;
|
||||||
|
NameLocalized = cmd.NameLocalized;
|
||||||
|
DescriptionLocalized = cmd.DescriptionLocalized;
|
||||||
}
|
}
|
||||||
public ApplicationCommandOption(ApplicationCommandOptionProperties option)
|
public ApplicationCommandOption(ApplicationCommandOptionProperties option)
|
||||||
{
|
{
|
||||||
@@ -94,6 +112,9 @@ namespace Discord.API
|
|||||||
Type = option.Type;
|
Type = option.Type;
|
||||||
Description = option.Description;
|
Description = option.Description;
|
||||||
Autocomplete = option.IsAutocomplete;
|
Autocomplete = option.IsAutocomplete;
|
||||||
|
|
||||||
|
NameLocalizations = option.NameLocalizations?.ToDictionary() ?? Optional<Dictionary<string, string>>.Unspecified;
|
||||||
|
DescriptionLocalizations = option.DescriptionLocalizations?.ToDictionary() ?? Optional<Dictionary<string, string>>.Unspecified;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Discord.API
|
namespace Discord.API
|
||||||
{
|
{
|
||||||
@@ -9,5 +10,11 @@ namespace Discord.API
|
|||||||
|
|
||||||
[JsonProperty("value")]
|
[JsonProperty("value")]
|
||||||
public object Value { get; set; }
|
public object Value { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("name_localizations")]
|
||||||
|
public Optional<Dictionary<string, string>> NameLocalizations { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("name_localized")]
|
||||||
|
public Optional<string> NameLocalized { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Discord.API.Rest
|
namespace Discord.API.Rest
|
||||||
{
|
{
|
||||||
@@ -19,6 +23,12 @@ namespace Discord.API.Rest
|
|||||||
[JsonProperty("default_permission")]
|
[JsonProperty("default_permission")]
|
||||||
public Optional<bool> DefaultPermission { get; set; }
|
public Optional<bool> DefaultPermission { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("name_localizations")]
|
||||||
|
public Optional<Dictionary<string, string>> NameLocalizations { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("description_localizations")]
|
||||||
|
public Optional<Dictionary<string, string>> DescriptionLocalizations { get; set; }
|
||||||
|
|
||||||
[JsonProperty("dm_permission")]
|
[JsonProperty("dm_permission")]
|
||||||
public Optional<bool?> DmPermission { get; set; }
|
public Optional<bool?> DmPermission { get; set; }
|
||||||
|
|
||||||
@@ -26,12 +36,15 @@ namespace Discord.API.Rest
|
|||||||
public Optional<GuildPermission?> DefaultMemberPermission { get; set; }
|
public Optional<GuildPermission?> DefaultMemberPermission { get; set; }
|
||||||
|
|
||||||
public CreateApplicationCommandParams() { }
|
public CreateApplicationCommandParams() { }
|
||||||
public CreateApplicationCommandParams(string name, string description, ApplicationCommandType type, ApplicationCommandOption[] options = null)
|
public CreateApplicationCommandParams(string name, string description, ApplicationCommandType type, ApplicationCommandOption[] options = null,
|
||||||
|
IDictionary<string, string> nameLocalizations = null, IDictionary<string, string> descriptionLocalizations = null)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
Description = description;
|
Description = description;
|
||||||
Options = Optional.Create(options);
|
Options = Optional.Create(options);
|
||||||
Type = type;
|
Type = type;
|
||||||
|
NameLocalizations = nameLocalizations?.ToDictionary(x => x.Key, x => x.Value) ?? Optional<Dictionary<string, string>>.Unspecified;
|
||||||
|
DescriptionLocalizations = descriptionLocalizations?.ToDictionary(x => x.Key, x => x.Value) ?? Optional<Dictionary<string, string>>.Unspecified;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Discord.API.Rest
|
namespace Discord.API.Rest
|
||||||
{
|
{
|
||||||
@@ -15,5 +16,11 @@ namespace Discord.API.Rest
|
|||||||
|
|
||||||
[JsonProperty("default_permission")]
|
[JsonProperty("default_permission")]
|
||||||
public Optional<bool> DefaultPermission { get; set; }
|
public Optional<bool> DefaultPermission { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("name_localizations")]
|
||||||
|
public Optional<Dictionary<string, string>> NameLocalizations { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("description_localizations")]
|
||||||
|
public Optional<Dictionary<string, string>> DescriptionLocalizations { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ namespace Discord.Rest
|
|||||||
=> Task.FromResult<IApplicationCommand>(null);
|
=> Task.FromResult<IApplicationCommand>(null);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
Task<IReadOnlyCollection<IApplicationCommand>> IDiscordClient.GetGlobalApplicationCommandsAsync(RequestOptions options)
|
Task<IReadOnlyCollection<IApplicationCommand>> IDiscordClient.GetGlobalApplicationCommandsAsync(bool withLocalizations, string locale, RequestOptions options)
|
||||||
=> Task.FromResult<IReadOnlyCollection<IApplicationCommand>>(ImmutableArray.Create<IApplicationCommand>());
|
=> Task.FromResult<IReadOnlyCollection<IApplicationCommand>>(ImmutableArray.Create<IApplicationCommand>());
|
||||||
Task<IApplicationCommand> IDiscordClient.CreateGlobalApplicationCommand(ApplicationCommandProperties properties, RequestOptions options)
|
Task<IApplicationCommand> IDiscordClient.CreateGlobalApplicationCommand(ApplicationCommandProperties properties, RequestOptions options)
|
||||||
=> Task.FromResult<IApplicationCommand>(null);
|
=> Task.FromResult<IApplicationCommand>(null);
|
||||||
|
|||||||
@@ -194,10 +194,10 @@ namespace Discord.Rest
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<IReadOnlyCollection<RestGlobalCommand>> GetGlobalApplicationCommandsAsync(BaseDiscordClient client,
|
public static async Task<IReadOnlyCollection<RestGlobalCommand>> GetGlobalApplicationCommandsAsync(BaseDiscordClient client, bool withLocalizations = false,
|
||||||
RequestOptions options = null)
|
string locale = null, RequestOptions options = null)
|
||||||
{
|
{
|
||||||
var response = await client.ApiClient.GetGlobalApplicationCommandsAsync(options).ConfigureAwait(false);
|
var response = await client.ApiClient.GetGlobalApplicationCommandsAsync(withLocalizations, locale, options).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!response.Any())
|
if (!response.Any())
|
||||||
return Array.Empty<RestGlobalCommand>();
|
return Array.Empty<RestGlobalCommand>();
|
||||||
@@ -212,10 +212,10 @@ namespace Discord.Rest
|
|||||||
return model != null ? RestGlobalCommand.Create(client, model) : null;
|
return model != null ? RestGlobalCommand.Create(client, model) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<IReadOnlyCollection<RestGuildCommand>> GetGuildApplicationCommandsAsync(BaseDiscordClient client, ulong guildId,
|
public static async Task<IReadOnlyCollection<RestGuildCommand>> GetGuildApplicationCommandsAsync(BaseDiscordClient client, ulong guildId, bool withLocalizations = false,
|
||||||
RequestOptions options = null)
|
string locale = null, RequestOptions options = null)
|
||||||
{
|
{
|
||||||
var response = await client.ApiClient.GetGuildApplicationCommandsAsync(guildId, options).ConfigureAwait(false);
|
var response = await client.ApiClient.GetGuildApplicationCommandsAsync(guildId, withLocalizations, locale, options).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!response.Any())
|
if (!response.Any())
|
||||||
return ImmutableArray.Create<RestGuildCommand>();
|
return ImmutableArray.Create<RestGuildCommand>();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using Newtonsoft.Json;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.Design;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -1212,11 +1213,22 @@ namespace Discord.API
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Interactions
|
#region Interactions
|
||||||
public async Task<ApplicationCommand[]> GetGlobalApplicationCommandsAsync(RequestOptions options = null)
|
public async Task<ApplicationCommand[]> GetGlobalApplicationCommandsAsync(bool withLocalizations = false, string locale = null, RequestOptions options = null)
|
||||||
{
|
{
|
||||||
options = RequestOptions.CreateOrClone(options);
|
options = RequestOptions.CreateOrClone(options);
|
||||||
|
|
||||||
return await SendAsync<ApplicationCommand[]>("GET", () => $"applications/{CurrentApplicationId}/commands", new BucketIds(), options: options).ConfigureAwait(false);
|
if (locale is not null)
|
||||||
|
{
|
||||||
|
if (!System.Text.RegularExpressions.Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException($"{locale} is not a valid locale.", nameof(locale));
|
||||||
|
|
||||||
|
options.RequestHeaders["X-Discord-Locale"] = new[] { locale };
|
||||||
|
}
|
||||||
|
|
||||||
|
//with_localizations=false doesnt return localized names and descriptions
|
||||||
|
var query = withLocalizations ? "?with_localizations=true" : string.Empty;
|
||||||
|
return await SendAsync<ApplicationCommand[]>("GET", () => $"applications/{CurrentApplicationId}/commands{query}",
|
||||||
|
new BucketIds(), options: options).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ApplicationCommand> GetGlobalApplicationCommandAsync(ulong id, RequestOptions options = null)
|
public async Task<ApplicationCommand> GetGlobalApplicationCommandAsync(ulong id, RequestOptions options = null)
|
||||||
@@ -1281,13 +1293,24 @@ namespace Discord.API
|
|||||||
return await SendJsonAsync<ApplicationCommand[]>("PUT", () => $"applications/{CurrentApplicationId}/commands", commands, new BucketIds(), options: options).ConfigureAwait(false);
|
return await SendJsonAsync<ApplicationCommand[]>("PUT", () => $"applications/{CurrentApplicationId}/commands", commands, new BucketIds(), options: options).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ApplicationCommand[]> GetGuildApplicationCommandsAsync(ulong guildId, RequestOptions options = null)
|
public async Task<ApplicationCommand[]> GetGuildApplicationCommandsAsync(ulong guildId, bool withLocalizations = false, string locale = null, RequestOptions options = null)
|
||||||
{
|
{
|
||||||
options = RequestOptions.CreateOrClone(options);
|
options = RequestOptions.CreateOrClone(options);
|
||||||
|
|
||||||
var bucket = new BucketIds(guildId: guildId);
|
var bucket = new BucketIds(guildId: guildId);
|
||||||
|
|
||||||
return await SendAsync<ApplicationCommand[]>("GET", () => $"applications/{CurrentApplicationId}/guilds/{guildId}/commands", bucket, options: options).ConfigureAwait(false);
|
if (locale is not null)
|
||||||
|
{
|
||||||
|
if (!System.Text.RegularExpressions.Regex.IsMatch(locale, @"^\w{2}(?:-\w{2})?$"))
|
||||||
|
throw new ArgumentException($"{locale} is not a valid locale.", nameof(locale));
|
||||||
|
|
||||||
|
options.RequestHeaders["X-Discord-Locale"] = new[] { locale };
|
||||||
|
}
|
||||||
|
|
||||||
|
//with_localizations=false doesnt return localized names and descriptions
|
||||||
|
var query = withLocalizations ? "?with_localizations=true" : string.Empty;
|
||||||
|
return await SendAsync<ApplicationCommand[]>("GET", () => $"applications/{CurrentApplicationId}/guilds/{guildId}/commands{query}",
|
||||||
|
bucket, options: options).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ApplicationCommand> GetGuildApplicationCommandAsync(ulong guildId, ulong commandId, RequestOptions options = null)
|
public async Task<ApplicationCommand> GetGuildApplicationCommandAsync(ulong guildId, ulong commandId, RequestOptions options = null)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ namespace Discord.Rest
|
|||||||
/// Gets the logged-in user.
|
/// Gets the logged-in user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public new RestSelfUser CurrentUser { get => base.CurrentUser as RestSelfUser; internal set => base.CurrentUser = value; }
|
public new RestSelfUser CurrentUser { get => base.CurrentUser as RestSelfUser; internal set => base.CurrentUser = value; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public DiscordRestClient() : this(new DiscordRestConfig()) { }
|
public DiscordRestClient() : this(new DiscordRestConfig()) { }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -205,10 +205,10 @@ namespace Discord.Rest
|
|||||||
=> ClientHelper.CreateGlobalApplicationCommandAsync(this, properties, options);
|
=> ClientHelper.CreateGlobalApplicationCommandAsync(this, properties, options);
|
||||||
public Task<RestGuildCommand> CreateGuildCommand(ApplicationCommandProperties properties, ulong guildId, RequestOptions options = null)
|
public Task<RestGuildCommand> CreateGuildCommand(ApplicationCommandProperties properties, ulong guildId, RequestOptions options = null)
|
||||||
=> ClientHelper.CreateGuildApplicationCommandAsync(this, guildId, properties, options);
|
=> ClientHelper.CreateGuildApplicationCommandAsync(this, guildId, properties, options);
|
||||||
public Task<IReadOnlyCollection<RestGlobalCommand>> GetGlobalApplicationCommands(RequestOptions options = null)
|
public Task<IReadOnlyCollection<RestGlobalCommand>> GetGlobalApplicationCommands(bool withLocalizations = false, string locale = null, RequestOptions options = null)
|
||||||
=> ClientHelper.GetGlobalApplicationCommandsAsync(this, options);
|
=> ClientHelper.GetGlobalApplicationCommandsAsync(this, withLocalizations, locale, options);
|
||||||
public Task<IReadOnlyCollection<RestGuildCommand>> GetGuildApplicationCommands(ulong guildId, RequestOptions options = null)
|
public Task<IReadOnlyCollection<RestGuildCommand>> GetGuildApplicationCommands(ulong guildId, bool withLocalizations = false, string locale = null, RequestOptions options = null)
|
||||||
=> ClientHelper.GetGuildApplicationCommandsAsync(this, guildId, options);
|
=> ClientHelper.GetGuildApplicationCommandsAsync(this, guildId, withLocalizations, locale, options);
|
||||||
public Task<IReadOnlyCollection<RestGlobalCommand>> BulkOverwriteGlobalCommands(ApplicationCommandProperties[] commandProperties, RequestOptions options = null)
|
public Task<IReadOnlyCollection<RestGlobalCommand>> BulkOverwriteGlobalCommands(ApplicationCommandProperties[] commandProperties, RequestOptions options = null)
|
||||||
=> ClientHelper.BulkOverwriteGlobalApplicationCommandAsync(this, commandProperties, options);
|
=> ClientHelper.BulkOverwriteGlobalApplicationCommandAsync(this, commandProperties, options);
|
||||||
public Task<IReadOnlyCollection<RestGuildCommand>> BulkOverwriteGuildCommands(ApplicationCommandProperties[] commandProperties, ulong guildId, RequestOptions options = null)
|
public Task<IReadOnlyCollection<RestGuildCommand>> BulkOverwriteGuildCommands(ApplicationCommandProperties[] commandProperties, ulong guildId, RequestOptions options = null)
|
||||||
@@ -319,8 +319,8 @@ namespace Discord.Rest
|
|||||||
=> await GetWebhookAsync(id, options).ConfigureAwait(false);
|
=> await GetWebhookAsync(id, options).ConfigureAwait(false);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
async Task<IReadOnlyCollection<IApplicationCommand>> IDiscordClient.GetGlobalApplicationCommandsAsync(RequestOptions options)
|
async Task<IReadOnlyCollection<IApplicationCommand>> IDiscordClient.GetGlobalApplicationCommandsAsync(bool withLocalizations, string locale, RequestOptions options)
|
||||||
=> await GetGlobalApplicationCommands(options).ConfigureAwait(false);
|
=> await GetGlobalApplicationCommands(withLocalizations, locale, options).ConfigureAwait(false);
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
async Task<IApplicationCommand> IDiscordClient.GetGlobalApplicationCommandAsync(ulong id, RequestOptions options)
|
async Task<IApplicationCommand> IDiscordClient.GetGlobalApplicationCommandAsync(ulong id, RequestOptions options)
|
||||||
=> await ClientHelper.GetGlobalApplicationCommandAsync(this, id, options).ConfigureAwait(false);
|
=> await ClientHelper.GetGlobalApplicationCommandAsync(this, id, options).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -362,10 +362,10 @@ namespace Discord.Rest
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Interactions
|
#region Interactions
|
||||||
public static async Task<IReadOnlyCollection<RestGuildCommand>> GetSlashCommandsAsync(IGuild guild, BaseDiscordClient client,
|
public static async Task<IReadOnlyCollection<RestGuildCommand>> GetSlashCommandsAsync(IGuild guild, BaseDiscordClient client, bool withLocalizations,
|
||||||
RequestOptions options)
|
string locale, RequestOptions options)
|
||||||
{
|
{
|
||||||
var models = await client.ApiClient.GetGuildApplicationCommandsAsync(guild.Id, options);
|
var models = await client.ApiClient.GetGuildApplicationCommandsAsync(guild.Id, withLocalizations, locale, options);
|
||||||
return models.Select(x => RestGuildCommand.Create(client, x, guild.Id)).ToImmutableArray();
|
return models.Select(x => RestGuildCommand.Create(client, x, guild.Id)).ToImmutableArray();
|
||||||
}
|
}
|
||||||
public static async Task<RestGuildCommand> GetSlashCommandAsync(IGuild guild, ulong id, BaseDiscordClient client,
|
public static async Task<RestGuildCommand> GetSlashCommandAsync(IGuild guild, ulong id, BaseDiscordClient client,
|
||||||
|
|||||||
@@ -311,13 +311,15 @@ namespace Discord.Rest
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a collection of slash commands created by the current user in this guild.
|
/// Gets a collection of slash commands created by the current user in this guild.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="withLocalizations">Whether to include full localization dictionaries in the returned objects, instead of the name localized and description localized fields.</param>
|
||||||
|
/// <param name="locale">The target locale of the localized name and description fields. Sets <c>X-Discord-Locale</c> header, which takes precedence over <c>Accept-Language</c>.</param>
|
||||||
/// <param name="options">The options to be used when sending the request.</param>
|
/// <param name="options">The options to be used when sending the request.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of
|
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of
|
||||||
/// slash commands created by the current user.
|
/// slash commands created by the current user.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public Task<IReadOnlyCollection<RestGuildCommand>> GetSlashCommandsAsync(RequestOptions options = null)
|
public Task<IReadOnlyCollection<RestGuildCommand>> GetSlashCommandsAsync(bool withLocalizations = false, string locale = null, RequestOptions options = null)
|
||||||
=> GuildHelper.GetSlashCommandsAsync(this, Discord, options);
|
=> GuildHelper.GetSlashCommandsAsync(this, Discord, withLocalizations, locale, options);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a slash command in the current guild.
|
/// Gets a slash command in the current guild.
|
||||||
@@ -928,13 +930,15 @@ namespace Discord.Rest
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets this guilds slash commands
|
/// Gets this guilds slash commands
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="withLocalizations">Whether to include full localization dictionaries in the returned objects, instead of the name localized and description localized fields.</param>
|
||||||
|
/// <param name="locale">The target locale of the localized name and description fields. Sets <c>X-Discord-Locale</c> header, which takes precedence over <c>Accept-Language</c>.</param>
|
||||||
/// <param name="options">The options to be used when sending the request.</param>
|
/// <param name="options">The options to be used when sending the request.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// A task that represents the asynchronous get operation. The task result contains a read-only collection
|
/// A task that represents the asynchronous get operation. The task result contains a read-only collection
|
||||||
/// of application commands found within the guild.
|
/// of application commands found within the guild.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public async Task<IReadOnlyCollection<RestGuildCommand>> GetApplicationCommandsAsync (RequestOptions options = null)
|
public async Task<IReadOnlyCollection<RestGuildCommand>> GetApplicationCommandsAsync (bool withLocalizations = false, string locale = null, RequestOptions options = null)
|
||||||
=> await ClientHelper.GetGuildApplicationCommandsAsync(Discord, Id, options).ConfigureAwait(false);
|
=> await ClientHelper.GetGuildApplicationCommandsAsync(Discord, Id, withLocalizations, locale, options).ConfigureAwait(false);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an application command within this guild with the specified id.
|
/// Gets an application command within this guild with the specified id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1467,8 +1471,8 @@ namespace Discord.Rest
|
|||||||
async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options)
|
async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options)
|
||||||
=> await GetWebhooksAsync(options).ConfigureAwait(false);
|
=> await GetWebhooksAsync(options).ConfigureAwait(false);
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
async Task<IReadOnlyCollection<IApplicationCommand>> IGuild.GetApplicationCommandsAsync (RequestOptions options)
|
async Task<IReadOnlyCollection<IApplicationCommand>> IGuild.GetApplicationCommandsAsync (bool withLocalizations, string locale, RequestOptions options)
|
||||||
=> await GetApplicationCommandsAsync(options).ConfigureAwait(false);
|
=> await GetApplicationCommandsAsync(withLocalizations, locale, options).ConfigureAwait(false);
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
async Task<ICustomSticker> IGuild.CreateStickerAsync(string name, string description, IEnumerable<string> tags, Image image, RequestOptions options)
|
async Task<ICustomSticker> IGuild.CreateStickerAsync(string name, string description, IEnumerable<string> tags, Image image, RequestOptions options)
|
||||||
=> await CreateStickerAsync(name, description, tags, image, options);
|
=> await CreateStickerAsync(name, description, tags, image, options);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Discord.API.Rest;
|
|||||||
using Discord.Net;
|
using Discord.Net;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -101,11 +102,12 @@ namespace Discord.Rest
|
|||||||
DefaultPermission = arg.IsDefaultPermission.IsSpecified
|
DefaultPermission = arg.IsDefaultPermission.IsSpecified
|
||||||
? arg.IsDefaultPermission.Value
|
? arg.IsDefaultPermission.Value
|
||||||
: Optional<bool>.Unspecified,
|
: Optional<bool>.Unspecified,
|
||||||
|
NameLocalizations = arg.NameLocalizations?.ToDictionary(),
|
||||||
|
DescriptionLocalizations = arg.DescriptionLocalizations?.ToDictionary(),
|
||||||
|
|
||||||
// TODO: better conversion to nullable optionals
|
// TODO: better conversion to nullable optionals
|
||||||
DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(),
|
DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(),
|
||||||
DmPermission = arg.IsDMEnabled.ToNullable()
|
DmPermission = arg.IsDMEnabled.ToNullable()
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (arg is SlashCommandProperties slashProps)
|
if (arg is SlashCommandProperties slashProps)
|
||||||
@@ -140,12 +142,16 @@ namespace Discord.Rest
|
|||||||
DefaultPermission = arg.IsDefaultPermission.IsSpecified
|
DefaultPermission = arg.IsDefaultPermission.IsSpecified
|
||||||
? arg.IsDefaultPermission.Value
|
? arg.IsDefaultPermission.Value
|
||||||
: Optional<bool>.Unspecified,
|
: Optional<bool>.Unspecified,
|
||||||
|
NameLocalizations = arg.NameLocalizations?.ToDictionary(),
|
||||||
|
DescriptionLocalizations = arg.DescriptionLocalizations?.ToDictionary(),
|
||||||
|
|
||||||
// TODO: better conversion to nullable optionals
|
// TODO: better conversion to nullable optionals
|
||||||
DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(),
|
DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(),
|
||||||
DmPermission = arg.IsDMEnabled.ToNullable()
|
DmPermission = arg.IsDMEnabled.ToNullable()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Console.WriteLine("Locales:" + string.Join(",", arg.NameLocalizations.Keys));
|
||||||
|
|
||||||
if (arg is SlashCommandProperties slashProps)
|
if (arg is SlashCommandProperties slashProps)
|
||||||
{
|
{
|
||||||
Preconditions.NotNullOrEmpty(slashProps.Description, nameof(slashProps.Description));
|
Preconditions.NotNullOrEmpty(slashProps.Description, nameof(slashProps.Description));
|
||||||
@@ -181,6 +187,8 @@ namespace Discord.Rest
|
|||||||
DefaultPermission = arg.IsDefaultPermission.IsSpecified
|
DefaultPermission = arg.IsDefaultPermission.IsSpecified
|
||||||
? arg.IsDefaultPermission.Value
|
? arg.IsDefaultPermission.Value
|
||||||
: Optional<bool>.Unspecified,
|
: Optional<bool>.Unspecified,
|
||||||
|
NameLocalizations = arg.NameLocalizations?.ToDictionary(),
|
||||||
|
DescriptionLocalizations = arg.DescriptionLocalizations?.ToDictionary(),
|
||||||
|
|
||||||
// TODO: better conversion to nullable optionals
|
// TODO: better conversion to nullable optionals
|
||||||
DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(),
|
DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(),
|
||||||
@@ -244,7 +252,9 @@ namespace Discord.Rest
|
|||||||
Name = args.Name,
|
Name = args.Name,
|
||||||
DefaultPermission = args.IsDefaultPermission.IsSpecified
|
DefaultPermission = args.IsDefaultPermission.IsSpecified
|
||||||
? args.IsDefaultPermission.Value
|
? args.IsDefaultPermission.Value
|
||||||
: Optional<bool>.Unspecified
|
: Optional<bool>.Unspecified,
|
||||||
|
NameLocalizations = args.NameLocalizations?.ToDictionary(),
|
||||||
|
DescriptionLocalizations = args.DescriptionLocalizations?.ToDictionary()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (args is SlashCommandProperties slashProps)
|
if (args is SlashCommandProperties slashProps)
|
||||||
@@ -299,6 +309,8 @@ namespace Discord.Rest
|
|||||||
DefaultPermission = arg.IsDefaultPermission.IsSpecified
|
DefaultPermission = arg.IsDefaultPermission.IsSpecified
|
||||||
? arg.IsDefaultPermission.Value
|
? arg.IsDefaultPermission.Value
|
||||||
: Optional<bool>.Unspecified,
|
: Optional<bool>.Unspecified,
|
||||||
|
NameLocalizations = arg.NameLocalizations?.ToDictionary(),
|
||||||
|
DescriptionLocalizations = arg.DescriptionLocalizations?.ToDictionary(),
|
||||||
|
|
||||||
// TODO: better conversion to nullable optionals
|
// TODO: better conversion to nullable optionals
|
||||||
DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(),
|
DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(),
|
||||||
@@ -335,7 +347,9 @@ namespace Discord.Rest
|
|||||||
Name = arg.Name,
|
Name = arg.Name,
|
||||||
DefaultPermission = arg.IsDefaultPermission.IsSpecified
|
DefaultPermission = arg.IsDefaultPermission.IsSpecified
|
||||||
? arg.IsDefaultPermission.Value
|
? arg.IsDefaultPermission.Value
|
||||||
: Optional<bool>.Unspecified
|
: Optional<bool>.Unspecified,
|
||||||
|
NameLocalizations = arg.NameLocalizations?.ToDictionary(),
|
||||||
|
DescriptionLocalizations = arg.DescriptionLocalizations?.ToDictionary()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (arg is SlashCommandProperties slashProps)
|
if (arg is SlashCommandProperties slashProps)
|
||||||
|
|||||||
@@ -38,6 +38,32 @@ namespace Discord.Rest
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyCollection<RestApplicationCommandOption> Options { get; private set; }
|
public IReadOnlyCollection<RestApplicationCommandOption> Options { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the name field of this command.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> NameLocalizations { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the description field of this command.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> DescriptionLocalizations { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized name of this command.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only returned when the `withLocalizations` query parameter is set to <see langword="false"/> when requesting the command.
|
||||||
|
/// </remarks>
|
||||||
|
public string NameLocalized { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized description of this command.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only returned when the `withLocalizations` query parameter is set to <see langword="false"/> when requesting the command.
|
||||||
|
/// </remarks>
|
||||||
|
public string DescriptionLocalized { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public DateTimeOffset CreatedAt
|
public DateTimeOffset CreatedAt
|
||||||
=> SnowflakeUtils.FromSnowflake(Id);
|
=> SnowflakeUtils.FromSnowflake(Id);
|
||||||
@@ -64,6 +90,15 @@ namespace Discord.Rest
|
|||||||
? model.Options.Value.Select(RestApplicationCommandOption.Create).ToImmutableArray()
|
? model.Options.Value.Select(RestApplicationCommandOption.Create).ToImmutableArray()
|
||||||
: ImmutableArray.Create<RestApplicationCommandOption>();
|
: ImmutableArray.Create<RestApplicationCommandOption>();
|
||||||
|
|
||||||
|
NameLocalizations = model.NameLocalizations.GetValueOrDefault(null)?.ToImmutableDictionary() ??
|
||||||
|
ImmutableDictionary<string, string>.Empty;
|
||||||
|
|
||||||
|
DescriptionLocalizations = model.DescriptionLocalizations.GetValueOrDefault(null)?.ToImmutableDictionary() ??
|
||||||
|
ImmutableDictionary<string, string>.Empty;
|
||||||
|
|
||||||
|
NameLocalized = model.NameLocalized.GetValueOrDefault();
|
||||||
|
DescriptionLocalized = model.DescriptionLocalized.GetValueOrDefault();
|
||||||
|
|
||||||
IsEnabledInDm = model.DmPermission.GetValueOrDefault(true).GetValueOrDefault(true);
|
IsEnabledInDm = model.DmPermission.GetValueOrDefault(true).GetValueOrDefault(true);
|
||||||
DefaultMemberPermissions = new GuildPermissions((ulong)model.DefaultMemberPermission.GetValueOrDefault(0).GetValueOrDefault(0));
|
DefaultMemberPermissions = new GuildPermissions((ulong)model.DefaultMemberPermission.GetValueOrDefault(0).GetValueOrDefault(0));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using Model = Discord.API.ApplicationCommandOptionChoice;
|
using Model = Discord.API.ApplicationCommandOptionChoice;
|
||||||
|
|
||||||
namespace Discord.Rest
|
namespace Discord.Rest
|
||||||
@@ -13,10 +15,25 @@ namespace Discord.Rest
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public object Value { get; }
|
public object Value { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the name field of this command option choice.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> NameLocalizations { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized name of this command option choice.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only returned when the `withLocalizations` query parameter is set to <see langword="false"/> when requesting the command.
|
||||||
|
/// </remarks>
|
||||||
|
public string NameLocalized { get; }
|
||||||
|
|
||||||
internal RestApplicationCommandChoice(Model model)
|
internal RestApplicationCommandChoice(Model model)
|
||||||
{
|
{
|
||||||
Name = model.Name;
|
Name = model.Name;
|
||||||
Value = model.Value;
|
Value = model.Value;
|
||||||
|
NameLocalizations = model.NameLocalizations.GetValueOrDefault(null)?.ToImmutableDictionary();
|
||||||
|
NameLocalized = model.NameLocalized.GetValueOrDefault(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace Discord.Rest
|
|||||||
public bool? IsRequired { get; private set; }
|
public bool? IsRequired { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool? IsAutocomplete { get; private set; }
|
public bool? IsAutocomplete { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public double? MinValue { get; private set; }
|
public double? MinValue { get; private set; }
|
||||||
@@ -54,6 +54,32 @@ namespace Discord.Rest
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IReadOnlyCollection<ChannelType> ChannelTypes { get; private set; }
|
public IReadOnlyCollection<ChannelType> ChannelTypes { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the name field of this command option.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> NameLocalizations { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the description field of this command option.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> DescriptionLocalizations { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized name of this command option.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only returned when the `withLocalizations` query parameter is set to <see langword="false"/> when requesting the command.
|
||||||
|
/// </remarks>
|
||||||
|
public string NameLocalized { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized description of this command option.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only returned when the `withLocalizations` query parameter is set to <see langword="false"/> when requesting the command.
|
||||||
|
/// </remarks>
|
||||||
|
public string DescriptionLocalized { get; private set; }
|
||||||
|
|
||||||
internal RestApplicationCommandOption() { }
|
internal RestApplicationCommandOption() { }
|
||||||
|
|
||||||
internal static RestApplicationCommandOption Create(Model model)
|
internal static RestApplicationCommandOption Create(Model model)
|
||||||
@@ -98,6 +124,15 @@ namespace Discord.Rest
|
|||||||
ChannelTypes = model.ChannelTypes.IsSpecified
|
ChannelTypes = model.ChannelTypes.IsSpecified
|
||||||
? model.ChannelTypes.Value.ToImmutableArray()
|
? model.ChannelTypes.Value.ToImmutableArray()
|
||||||
: ImmutableArray.Create<ChannelType>();
|
: ImmutableArray.Create<ChannelType>();
|
||||||
|
|
||||||
|
NameLocalizations = model.NameLocalizations.GetValueOrDefault(null)?.ToImmutableDictionary() ??
|
||||||
|
ImmutableDictionary<string, string>.Empty;
|
||||||
|
|
||||||
|
DescriptionLocalizations = model.DescriptionLocalizations.GetValueOrDefault(null)?.ToImmutableDictionary() ??
|
||||||
|
ImmutableDictionary<string, string>.Empty;
|
||||||
|
|
||||||
|
NameLocalized = model.NameLocalized.GetValueOrDefault();
|
||||||
|
DescriptionLocalized = model.DescriptionLocalized.GetValueOrDefault();
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -66,33 +66,45 @@ namespace Discord.Net.Rest
|
|||||||
_cancelToken = cancelToken;
|
_cancelToken = cancelToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RestResponse> SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly, string reason = null)
|
public async Task<RestResponse> SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly, string reason = null,
|
||||||
|
IEnumerable<KeyValuePair<string, IEnumerable<string>>> requestHeaders = null)
|
||||||
{
|
{
|
||||||
string uri = Path.Combine(_baseUrl, endpoint);
|
string uri = Path.Combine(_baseUrl, endpoint);
|
||||||
using (var restRequest = new HttpRequestMessage(GetMethod(method), uri))
|
using (var restRequest = new HttpRequestMessage(GetMethod(method), uri))
|
||||||
{
|
{
|
||||||
if (reason != null) restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason));
|
if (reason != null) restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason));
|
||||||
|
if (requestHeaders != null)
|
||||||
|
foreach (var header in requestHeaders)
|
||||||
|
restRequest.Headers.Add(header.Key, header.Value);
|
||||||
return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false);
|
return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async Task<RestResponse> SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly, string reason = null)
|
public async Task<RestResponse> SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly, string reason = null,
|
||||||
|
IEnumerable<KeyValuePair<string, IEnumerable<string>>> requestHeaders = null)
|
||||||
{
|
{
|
||||||
string uri = Path.Combine(_baseUrl, endpoint);
|
string uri = Path.Combine(_baseUrl, endpoint);
|
||||||
using (var restRequest = new HttpRequestMessage(GetMethod(method), uri))
|
using (var restRequest = new HttpRequestMessage(GetMethod(method), uri))
|
||||||
{
|
{
|
||||||
if (reason != null) restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason));
|
if (reason != null) restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason));
|
||||||
|
if (requestHeaders != null)
|
||||||
|
foreach (var header in requestHeaders)
|
||||||
|
restRequest.Headers.Add(header.Key, header.Value);
|
||||||
restRequest.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
restRequest.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false);
|
return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <exception cref="InvalidOperationException">Unsupported param type.</exception>
|
/// <exception cref="InvalidOperationException">Unsupported param type.</exception>
|
||||||
public async Task<RestResponse> SendAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartParams, CancellationToken cancelToken, bool headerOnly, string reason = null)
|
public async Task<RestResponse> SendAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartParams, CancellationToken cancelToken, bool headerOnly, string reason = null,
|
||||||
|
IEnumerable<KeyValuePair<string, IEnumerable<string>>> requestHeaders = null)
|
||||||
{
|
{
|
||||||
string uri = Path.Combine(_baseUrl, endpoint);
|
string uri = Path.Combine(_baseUrl, endpoint);
|
||||||
using (var restRequest = new HttpRequestMessage(GetMethod(method), uri))
|
using (var restRequest = new HttpRequestMessage(GetMethod(method), uri))
|
||||||
{
|
{
|
||||||
if (reason != null) restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason));
|
if (reason != null) restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason));
|
||||||
|
if (requestHeaders != null)
|
||||||
|
foreach (var header in requestHeaders)
|
||||||
|
restRequest.Headers.Add(header.Key, header.Value);
|
||||||
var content = new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture));
|
var content = new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture));
|
||||||
MemoryStream memoryStream = null;
|
MemoryStream memoryStream = null;
|
||||||
if (multipartParams != null)
|
if (multipartParams != null)
|
||||||
@@ -126,7 +138,7 @@ namespace Discord.Net.Rest
|
|||||||
|
|
||||||
content.Add(streamContent, p.Key, fileValue.Filename);
|
content.Add(streamContent, p.Key, fileValue.Filename);
|
||||||
#pragma warning restore IDISP004
|
#pragma warning restore IDISP004
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
using Discord.Net.Rest;
|
using Discord.Net.Rest;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -28,7 +31,7 @@ namespace Discord.Net.Queue
|
|||||||
|
|
||||||
public virtual async Task<RestResponse> SendAsync()
|
public virtual async Task<RestResponse> SendAsync()
|
||||||
{
|
{
|
||||||
return await Client.SendAsync(Method, Endpoint, Options.CancelToken, Options.HeaderOnly, Options.AuditLogReason).ConfigureAwait(false);
|
return await Client.SendAsync(Method, Endpoint, Options.CancelToken, Options.HeaderOnly, Options.AuditLogReason, Options.RequestHeaders).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -450,14 +450,16 @@ namespace Discord.WebSocket
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a collection of all global commands.
|
/// Gets a collection of all global commands.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="withLocalizations">Whether to include full localization dictionaries in the returned objects, instead of the name localized and description localized fields.</param>
|
||||||
|
/// <param name="locale">The target locale of the localized name and description fields. Sets <c>X-Discord-Locale</c> header, which takes precedence over <c>Accept-Language</c>.</param>
|
||||||
/// <param name="options">The options to be used when sending the request.</param>
|
/// <param name="options">The options to be used when sending the request.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of global
|
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of global
|
||||||
/// application commands.
|
/// application commands.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public async Task<IReadOnlyCollection<SocketApplicationCommand>> GetGlobalApplicationCommandsAsync(RequestOptions options = null)
|
public async Task<IReadOnlyCollection<SocketApplicationCommand>> GetGlobalApplicationCommandsAsync(bool withLocalizations = false, string locale = null, RequestOptions options = null)
|
||||||
{
|
{
|
||||||
var commands = (await ApiClient.GetGlobalApplicationCommandsAsync(options)).Select(x => SocketApplicationCommand.Create(this, x));
|
var commands = (await ApiClient.GetGlobalApplicationCommandsAsync(withLocalizations, locale, options)).Select(x => SocketApplicationCommand.Create(this, x));
|
||||||
|
|
||||||
foreach(var command in commands)
|
foreach(var command in commands)
|
||||||
{
|
{
|
||||||
@@ -3236,8 +3238,8 @@ namespace Discord.WebSocket
|
|||||||
async Task<IApplicationCommand> IDiscordClient.GetGlobalApplicationCommandAsync(ulong id, RequestOptions options)
|
async Task<IApplicationCommand> IDiscordClient.GetGlobalApplicationCommandAsync(ulong id, RequestOptions options)
|
||||||
=> await GetGlobalApplicationCommandAsync(id, options);
|
=> await GetGlobalApplicationCommandAsync(id, options);
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
async Task<IReadOnlyCollection<IApplicationCommand>> IDiscordClient.GetGlobalApplicationCommandsAsync(RequestOptions options)
|
async Task<IReadOnlyCollection<IApplicationCommand>> IDiscordClient.GetGlobalApplicationCommandsAsync(bool withLocalizations, string locale, RequestOptions options)
|
||||||
=> await GetGlobalApplicationCommandsAsync(options);
|
=> await GetGlobalApplicationCommandsAsync(withLocalizations, locale, options);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
async Task IDiscordClient.StartAsync()
|
async Task IDiscordClient.StartAsync()
|
||||||
|
|||||||
@@ -874,14 +874,17 @@ namespace Discord.WebSocket
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a collection of slash commands created by the current user in this guild.
|
/// Gets a collection of slash commands created by the current user in this guild.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="withLocalizations">Whether to include full localization dictionaries in the returned objects, instead of the name localized and description localized fields.</param>
|
||||||
|
/// <param name="locale">The target locale of the localized name and description fields. Sets <c>X-Discord-Locale</c> header, which takes precedence over <c>Accept-Language</c>.</param>
|
||||||
/// <param name="options">The options to be used when sending the request.</param>
|
/// <param name="options">The options to be used when sending the request.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of
|
/// A task that represents the asynchronous get operation. The task result contains a read-only collection of
|
||||||
/// slash commands created by the current user.
|
/// slash commands created by the current user.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public async Task<IReadOnlyCollection<SocketApplicationCommand>> GetApplicationCommandsAsync(RequestOptions options = null)
|
public async Task<IReadOnlyCollection<SocketApplicationCommand>> GetApplicationCommandsAsync(bool withLocalizations = false, string locale = null, RequestOptions options = null)
|
||||||
{
|
{
|
||||||
var commands = (await Discord.ApiClient.GetGuildApplicationCommandsAsync(Id, options)).Select(x => SocketApplicationCommand.Create(Discord, x, Id));
|
var commands = (await Discord.ApiClient.GetGuildApplicationCommandsAsync(Id, withLocalizations, locale, options))
|
||||||
|
.Select(x => SocketApplicationCommand.Create(Discord, x, Id));
|
||||||
|
|
||||||
foreach (var command in commands)
|
foreach (var command in commands)
|
||||||
{
|
{
|
||||||
@@ -1977,8 +1980,8 @@ namespace Discord.WebSocket
|
|||||||
async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options)
|
async Task<IReadOnlyCollection<IWebhook>> IGuild.GetWebhooksAsync(RequestOptions options)
|
||||||
=> await GetWebhooksAsync(options).ConfigureAwait(false);
|
=> await GetWebhooksAsync(options).ConfigureAwait(false);
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
async Task<IReadOnlyCollection<IApplicationCommand>> IGuild.GetApplicationCommandsAsync (RequestOptions options)
|
async Task<IReadOnlyCollection<IApplicationCommand>> IGuild.GetApplicationCommandsAsync (bool withLocalizations, string locale, RequestOptions options)
|
||||||
=> await GetApplicationCommandsAsync(options).ConfigureAwait(false);
|
=> await GetApplicationCommandsAsync(withLocalizations, locale, options).ConfigureAwait(false);
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
async Task<ICustomSticker> IGuild.CreateStickerAsync(string name, string description, IEnumerable<string> tags, Image image, RequestOptions options)
|
async Task<ICustomSticker> IGuild.CreateStickerAsync(string name, string description, IEnumerable<string> tags, Image image, RequestOptions options)
|
||||||
=> await CreateStickerAsync(name, description, tags, image, options);
|
=> await CreateStickerAsync(name, description, tags, image, options);
|
||||||
|
|||||||
@@ -50,6 +50,32 @@ namespace Discord.WebSocket
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public IReadOnlyCollection<SocketApplicationCommandOption> Options { get; private set; }
|
public IReadOnlyCollection<SocketApplicationCommandOption> Options { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the name field of this command.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> NameLocalizations { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the description field of this command.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> DescriptionLocalizations { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized name of this command.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only returned when the `withLocalizations` query parameter is set to <see langword="false"/> when requesting the command.
|
||||||
|
/// </remarks>
|
||||||
|
public string NameLocalized { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized description of this command.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only returned when the `withLocalizations` query parameter is set to <see langword="false"/> when requesting the command.
|
||||||
|
/// </remarks>
|
||||||
|
public string DescriptionLocalized { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public DateTimeOffset CreatedAt
|
public DateTimeOffset CreatedAt
|
||||||
=> SnowflakeUtils.FromSnowflake(Id);
|
=> SnowflakeUtils.FromSnowflake(Id);
|
||||||
@@ -93,6 +119,15 @@ namespace Discord.WebSocket
|
|||||||
? model.Options.Value.Select(SocketApplicationCommandOption.Create).ToImmutableArray()
|
? model.Options.Value.Select(SocketApplicationCommandOption.Create).ToImmutableArray()
|
||||||
: ImmutableArray.Create<SocketApplicationCommandOption>();
|
: ImmutableArray.Create<SocketApplicationCommandOption>();
|
||||||
|
|
||||||
|
NameLocalizations = model.NameLocalizations.GetValueOrDefault(null)?.ToImmutableDictionary() ??
|
||||||
|
ImmutableDictionary<string, string>.Empty;
|
||||||
|
|
||||||
|
DescriptionLocalizations = model.DescriptionLocalizations.GetValueOrDefault(null)?.ToImmutableDictionary() ??
|
||||||
|
ImmutableDictionary<string, string>.Empty;
|
||||||
|
|
||||||
|
NameLocalized = model.NameLocalized.GetValueOrDefault();
|
||||||
|
DescriptionLocalized = model.DescriptionLocalized.GetValueOrDefault();
|
||||||
|
|
||||||
IsEnabledInDm = model.DmPermission.GetValueOrDefault(true).GetValueOrDefault(true);
|
IsEnabledInDm = model.DmPermission.GetValueOrDefault(true).GetValueOrDefault(true);
|
||||||
DefaultMemberPermissions = new GuildPermissions((ulong)model.DefaultMemberPermission.GetValueOrDefault(0).GetValueOrDefault(0));
|
DefaultMemberPermissions = new GuildPermissions((ulong)model.DefaultMemberPermission.GetValueOrDefault(0).GetValueOrDefault(0));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using Model = Discord.API.ApplicationCommandOptionChoice;
|
using Model = Discord.API.ApplicationCommandOptionChoice;
|
||||||
|
|
||||||
namespace Discord.WebSocket
|
namespace Discord.WebSocket
|
||||||
@@ -13,6 +15,19 @@ namespace Discord.WebSocket
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public object Value { get; private set; }
|
public object Value { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the name field of this command option choice.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> NameLocalizations { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized name of this command option choice.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only returned when the `withLocalizations` query parameter is set to <see langword="false"/> when requesting the command.
|
||||||
|
/// </remarks>
|
||||||
|
public string NameLocalized { get; private set; }
|
||||||
|
|
||||||
internal SocketApplicationCommandChoice() { }
|
internal SocketApplicationCommandChoice() { }
|
||||||
internal static SocketApplicationCommandChoice Create(Model model)
|
internal static SocketApplicationCommandChoice Create(Model model)
|
||||||
{
|
{
|
||||||
@@ -24,6 +39,8 @@ namespace Discord.WebSocket
|
|||||||
{
|
{
|
||||||
Name = model.Name;
|
Name = model.Name;
|
||||||
Value = model.Value;
|
Value = model.Value;
|
||||||
|
NameLocalizations = model.NameLocalizations.GetValueOrDefault(null)?.ToImmutableDictionary();
|
||||||
|
NameLocalized = model.NameLocalized.GetValueOrDefault(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,32 @@ namespace Discord.WebSocket
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyCollection<ChannelType> ChannelTypes { get; private set; }
|
public IReadOnlyCollection<ChannelType> ChannelTypes { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the name field of this command option.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> NameLocalizations { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localization dictionary for the description field of this command option.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyDictionary<string, string> DescriptionLocalizations { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized name of this command option.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only returned when the `withLocalizations` query parameter is set to <see langword="false"/> when requesting the command.
|
||||||
|
/// </remarks>
|
||||||
|
public string NameLocalized { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the localized description of this command option.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only returned when the `withLocalizations` query parameter is set to <see langword="false"/> when requesting the command.
|
||||||
|
/// </remarks>
|
||||||
|
public string DescriptionLocalized { get; private set; }
|
||||||
|
|
||||||
internal SocketApplicationCommandOption() { }
|
internal SocketApplicationCommandOption() { }
|
||||||
internal static SocketApplicationCommandOption Create(Model model)
|
internal static SocketApplicationCommandOption Create(Model model)
|
||||||
{
|
{
|
||||||
@@ -92,6 +118,15 @@ namespace Discord.WebSocket
|
|||||||
ChannelTypes = model.ChannelTypes.IsSpecified
|
ChannelTypes = model.ChannelTypes.IsSpecified
|
||||||
? model.ChannelTypes.Value.ToImmutableArray()
|
? model.ChannelTypes.Value.ToImmutableArray()
|
||||||
: ImmutableArray.Create<ChannelType>();
|
: ImmutableArray.Create<ChannelType>();
|
||||||
|
|
||||||
|
NameLocalizations = model.NameLocalizations.GetValueOrDefault(null)?.ToImmutableDictionary() ??
|
||||||
|
ImmutableDictionary<string, string>.Empty;
|
||||||
|
|
||||||
|
DescriptionLocalizations = model.DescriptionLocalizations.GetValueOrDefault(null)?.ToImmutableDictionary() ??
|
||||||
|
ImmutableDictionary<string, string>.Empty;
|
||||||
|
|
||||||
|
NameLocalized = model.NameLocalized.GetValueOrDefault();
|
||||||
|
DescriptionLocalized = model.DescriptionLocalized.GetValueOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
IReadOnlyCollection<IApplicationCommandOptionChoice> IApplicationCommandOption.Choices => Choices;
|
IReadOnlyCollection<IApplicationCommandOptionChoice> IApplicationCommandOption.Choices => Choices;
|
||||||
|
|||||||
Reference in New Issue
Block a user