Browse Source

add rest conversions to interaction service

pull/2211/head
Cenngo 3 years ago
parent
commit
1cea40ce8e
8 changed files with 190 additions and 38 deletions
  1. +53
    -0
      src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs
  2. +54
    -1
      src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs
  3. +1
    -1
      src/Discord.Net.Core/Extensions/GenericCollectionExtensions.cs
  4. +0
    -12
      src/Discord.Net.Interactions/ILocalizationManager.cs
  5. +12
    -0
      src/Discord.Net.Interactions/LocalizationManagers/ILocalizationManager.cs
  6. +6
    -6
      src/Discord.Net.Interactions/LocalizationManagers/JsonLocalizationManager.cs
  7. +4
    -4
      src/Discord.Net.Interactions/LocalizationManagers/ResxLocalizationManager.cs
  8. +60
    -14
      src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs

+ 53
- 0
src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs View File

@@ -1,3 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Discord namespace Discord
{ {
/// <summary> /// <summary>
@@ -31,7 +36,10 @@ namespace Discord
/// </summary> /// </summary>
public bool IsDefaultPermission { get; set; } = true; public bool IsDefaultPermission { get; set; } = true;


public IReadOnlyDictionary<string, string> NameLocalizations => _nameLocalizations;

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.
@@ -73,5 +81,50 @@ namespace Discord
IsDefaultPermission = isDefaultPermission; IsDefaultPermission = isDefaultPermission;
return this; return this;
} }

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;
}

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;
}

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.");
}
} }
} }

+ 54
- 1
src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs View File

@@ -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,7 +36,10 @@ namespace Discord
/// </summary> /// </summary>
public bool IsDefaultPermission { get; set; } = true; public bool IsDefaultPermission { get; set; } = true;


public IReadOnlyDictionary<string, string> NameLocalizations => _nameLocalizations;

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.
@@ -71,5 +79,50 @@ namespace Discord
IsDefaultPermission = isDefaultPermission; IsDefaultPermission = isDefaultPermission;
return this; return this;
} }

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;
}

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;
}

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.");
}
} }
} }

+ 1
- 1
src/Discord.Net.Core/Extensions/GenericCollectionExtensions.cs View File

@@ -1,4 +1,4 @@
namespace System.Collections.Generic;
namespace System.Collections.Generic;


public static class GenericCollectionExtensions public static class GenericCollectionExtensions
{ {


+ 0
- 12
src/Discord.Net.Interactions/ILocalizationManager.cs View File

@@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Discord.Interactions
{
public interface ILocalizationManager
{
Task<IDictionary<string, string>> GetAllNamesAsync(IList<string> key, LocalizationTarget destinationType);
Task<IDictionary<string, string>> GetAllDescriptionsAsync(IList<string> key, LocalizationTarget destinationType);
}
}

+ 12
- 0
src/Discord.Net.Interactions/LocalizationManagers/ILocalizationManager.cs View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Discord.Interactions
{
public interface ILocalizationManager
{
IDictionary<string, string> GetAllNames(IList<string> key, LocalizationTarget destinationType);
IDictionary<string, string> GetAllDescriptions(IList<string> key, LocalizationTarget destinationType);
}
}

+ 6
- 6
src/Discord.Net.Interactions/LocalizationManagers/JsonLocalizationManager.cs View File

@@ -23,16 +23,16 @@ namespace Discord.Interactions
_fileName = fileName; _fileName = fileName;
} }


public Task<IDictionary<string, string>> GetAllDescriptionsAsync(IList<string> key, LocalizationTarget destinationType) =>
GetValuesAsync(key, DescriptionIdentifier);
public IDictionary<string, string> GetAllDescriptions(IList<string> key, LocalizationTarget destinationType) =>
GetValues(key, DescriptionIdentifier);


public Task<IDictionary<string, string>> GetAllNamesAsync(IList<string> key, LocalizationTarget destinationType) =>
GetValuesAsync(key, NameIdentifier);
public IDictionary<string, string> GetAllNames(IList<string> key, LocalizationTarget destinationType) =>
GetValues(key, NameIdentifier);


private string[] GetAllFiles() => private string[] GetAllFiles() =>
Directory.GetFiles(_basePath, $"{_fileName}.*.json", SearchOption.TopDirectoryOnly); Directory.GetFiles(_basePath, $"{_fileName}.*.json", SearchOption.TopDirectoryOnly);


private async Task<IDictionary<string, string>> GetValuesAsync(IList<string> key, string identifier)
private IDictionary<string, string> GetValues(IList<string> key, string identifier)
{ {
var result = new Dictionary<string, string>(); var result = new Dictionary<string, string>();
var files = GetAllFiles(); var files = GetAllFiles();
@@ -47,7 +47,7 @@ namespace Discord.Interactions


using var sr = new StreamReader(file); using var sr = new StreamReader(file);
using var jr = new JsonTextReader(sr); using var jr = new JsonTextReader(sr);
var obj = await JObject.LoadAsync(jr);
var obj = JObject.Load(jr);
var token = string.Join(".", key) + $".{identifier}"; var token = string.Join(".", key) + $".{identifier}";
var value = (string)obj.SelectToken(token); var value = (string)obj.SelectToken(token);
if (value is not null) if (value is not null)


+ 4
- 4
src/Discord.Net.Interactions/LocalizationManagers/ResxLocalizationManager.cs View File

@@ -25,11 +25,11 @@ namespace Discord.Interactions
_supportedLocales = supportedLocales; _supportedLocales = supportedLocales;
} }


public Task<IDictionary<string, string>> GetAllDescriptionsAsync(IList<string> key, LocalizationTarget destinationType) =>
Task.FromResult(GetValues(key, DescriptionIdentifier));
public IDictionary<string, string> GetAllDescriptions(IList<string> key, LocalizationTarget destinationType) =>
GetValues(key, DescriptionIdentifier);


public Task<IDictionary<string, string>> GetAllNamesAsync(IList<string> key, LocalizationTarget destinationType) =>
Task.FromResult(GetValues(key, NameIdentifier));
public IDictionary<string, string> GetAllNames(IList<string> key, LocalizationTarget destinationType) =>
GetValues(key, NameIdentifier);


private IDictionary<string, string> GetValues(IList<string> key, string identifier) private IDictionary<string, string> GetValues(IList<string> key, string identifier)
{ {


+ 60
- 14
src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs View File

@@ -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,
@@ -23,7 +27,9 @@ namespace Discord.Interactions
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
}; };


parameterInfo.TypeConverter.Write(props, parameterInfo); parameterInfo.TypeConverter.Write(props, parameterInfo);
@@ -36,12 +42,17 @@ 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, IsDefaultPermission = commandInfo.DefaultPermission,
}.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");
@@ -51,23 +62,40 @@ namespace Discord.Interactions
return props; return props;
} }


public static ApplicationCommandOptionProperties ToApplicationCommandOptionProps(this SlashCommandInfo commandInfo) =>
new ApplicationCommandOptionProperties
public static ApplicationCommandOptionProperties ToApplicationCommandOptionProps(this SlashCommandInfo commandInfo)
{
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 { Name = commandInfo.Name, IsDefaultPermission = commandInfo.DefaultPermission}.Build(),
ApplicationCommandType.User => new UserCommandBuilder { Name = commandInfo.Name, IsDefaultPermission=commandInfo.DefaultPermission}.Build(),
ApplicationCommandType.Message => new MessageCommandBuilder { Name = commandInfo.Name, IsDefaultPermission = commandInfo.DefaultPermission}
.WithNameLocalizations(localizationManager?.GetAllNames(commandPath, LocalizationTarget.Command) ?? ImmutableDictionary<string, string>.Empty)
.Build(),
ApplicationCommandType.User => new UserCommandBuilder { Name = commandInfo.Name, IsDefaultPermission=commandInfo.DefaultPermission}
.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
@@ -108,12 +136,18 @@ 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,
Description = moduleInfo.Description, Description = moduleInfo.Description,
IsDefaultPermission = moduleInfo.DefaultPermission, IsDefaultPermission = moduleInfo.DefaultPermission,
}.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");
@@ -151,7 +185,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,
} }; } };
} }


@@ -166,17 +204,23 @@ 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
Options = command.Options?.Select(x => x.ToApplicationCommandOptionProps())?.ToList() ?? Optional<List<ApplicationCommandOptionProperties>>.Unspecified,
NameLocalizations = command.NameLocalizations?.ToImmutableDictionary(),
DescriptionLocalizations = command.DescriptionLocalizations?.ToImmutableDictionary(),
}, },
ApplicationCommandType.User => new UserCommandProperties ApplicationCommandType.User => new UserCommandProperties
{ {
Name = command.Name, Name = command.Name,
IsDefaultPermission = command.IsDefaultPermission
IsDefaultPermission = command.IsDefaultPermission,
NameLocalizations = command.NameLocalizations?.ToImmutableDictionary(),
DescriptionLocalizations = command.DescriptionLocalizations?.ToImmutableDictionary()
}, },
ApplicationCommandType.Message => new MessageCommandProperties ApplicationCommandType.Message => new MessageCommandProperties
{ {
Name = command.Name, Name = command.Name,
IsDefaultPermission = command.IsDefaultPermission
IsDefaultPermission = command.IsDefaultPermission,
NameLocalizations = command.NameLocalizations?.ToImmutableDictionary(),
DescriptionLocalizations = command.DescriptionLocalizations?.ToImmutableDictionary()
}, },
_ => throw new InvalidOperationException($"Cannot create command properties for command type {command.Type}"), _ => throw new InvalidOperationException($"Cannot create command properties for command type {command.Type}"),
}; };
@@ -194,7 +238,9 @@ namespace Discord.Interactions
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()
}; };
} }
} }

Loading…
Cancel
Save