Browse Source

Merge remote-tracking branch 'upstream/dev' into dev

pull/1199/head
ComputerMaster1st 6 years ago
parent
commit
2dfc54b7d7
21 changed files with 551 additions and 148 deletions
  1. +2
    -2
      docs/guides/voice/samples/joining_audio.cs
  2. +11
    -0
      src/Discord.Net.Commands/Attributes/NamedArgumentTypeAttribute.cs
  3. +2
    -3
      src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs
  4. +1
    -1
      src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
  5. +27
    -2
      src/Discord.Net.Commands/Builders/ParameterBuilder.cs
  6. +191
    -0
      src/Discord.Net.Commands/Readers/NamedArgumentTypeReader.cs
  7. +12
    -0
      src/Discord.Net.Core/Entities/Guilds/IGuild.cs
  8. +62
    -0
      src/Discord.Net.Core/Entities/Users/AddGuildUserProperties.cs
  9. +0
    -17
      src/Discord.Net.Core/Net/RpcException.cs
  10. +19
    -0
      src/Discord.Net.Rest/API/Rest/AddGuildMemberParams.cs
  11. +21
    -7
      src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs
  12. +19
    -0
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  13. +0
    -110
      src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs
  14. +28
    -0
      src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
  15. +8
    -0
      src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
  16. +1
    -1
      src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs
  17. +1
    -1
      src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs
  18. +1
    -1
      src/Discord.Net.Rest/Entities/Users/RestConnection.cs
  19. +8
    -0
      src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
  20. +4
    -3
      test/Discord.Net.Tests/Discord.Net.Tests.csproj
  21. +133
    -0
      test/Discord.Net.Tests/Tests.TypeReaders.cs

+ 2
- 2
docs/guides/voice/samples/joining_audio.cs View File

@@ -3,8 +3,8 @@
public async Task JoinChannel(IVoiceChannel channel = null)
{
// Get the audio channel
channel = channel ?? (msg.Author as IGuildUser)?.VoiceChannel;
if (channel == null) { await msg.Channel.SendMessageAsync("User must be in a voice channel, or a voice channel must be passed as an argument."); return; }
channel = channel ?? (Context.User as IGuildUser)?.VoiceChannel;
if (channel == null) { await Context.Channel.SendMessageAsync("User must be in a voice channel, or a voice channel must be passed as an argument."); return; }

// For the next step with transmitting audio, you would want to pass this Audio Client in to a service.
var audioClient = await channel.ConnectAsync();


+ 11
- 0
src/Discord.Net.Commands/Attributes/NamedArgumentTypeAttribute.cs View File

@@ -0,0 +1,11 @@
using System;

namespace Discord.Commands
{
/// <summary>
/// Instructs the command system to treat command paramters of this type
/// as a collection of named arguments matching to its properties.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class NamedArgumentTypeAttribute : Attribute { }
}

+ 2
- 3
src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs View File

@@ -1,5 +1,4 @@
using System;

using System.Reflection;

namespace Discord.Commands
@@ -27,8 +26,8 @@ namespace Discord.Commands
/// => ReplyAsync(time);
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class OverrideTypeReaderAttribute : Attribute
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class OverrideTypeReaderAttribute : Attribute
{
private static readonly TypeInfo TypeReaderTypeInfo = typeof(TypeReader).GetTypeInfo();



+ 1
- 1
src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs View File

@@ -280,7 +280,7 @@ namespace Discord.Commands
}
}

private static TypeReader GetTypeReader(CommandService service, Type paramType, Type typeReaderType, IServiceProvider services)
internal static TypeReader GetTypeReader(CommandService service, Type paramType, Type typeReaderType, IServiceProvider services)
{
var readers = service.GetTypeReaders(paramType);
TypeReader reader = null;


+ 27
- 2
src/Discord.Net.Commands/Builders/ParameterBuilder.cs View File

@@ -56,11 +56,36 @@ namespace Discord.Commands.Builders

private TypeReader GetReader(Type type)
{
var readers = Command.Module.Service.GetTypeReaders(type);
var commands = Command.Module.Service;
if (type.GetTypeInfo().GetCustomAttribute<NamedArgumentTypeAttribute>() != null)
{
IsRemainder = true;
var reader = commands.GetTypeReaders(type)?.FirstOrDefault().Value;
if (reader == null)
{
Type readerType;
try
{
readerType = typeof(NamedArgumentTypeReader<>).MakeGenericType(new[] { type });
}
catch (ArgumentException ex)
{
throw new InvalidOperationException($"Parameter type '{type.Name}' for command '{Command.Name}' must be a class with a public parameterless constructor to use as a NamedArgumentType.", ex);
}

reader = (TypeReader)Activator.CreateInstance(readerType, new[] { commands });
commands.AddTypeReader(type, reader);
}

return reader;
}


var readers = commands.GetTypeReaders(type);
if (readers != null)
return readers.FirstOrDefault().Value;
else
return Command.Module.Service.GetDefaultTypeReader(type);
return commands.GetDefaultTypeReader(type);
}

public ParameterBuilder WithSummary(string summary)


+ 191
- 0
src/Discord.Net.Commands/Readers/NamedArgumentTypeReader.cs View File

@@ -0,0 +1,191 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace Discord.Commands
{
internal sealed class NamedArgumentTypeReader<T> : TypeReader
where T : class, new()
{
private static readonly IReadOnlyDictionary<string, PropertyInfo> _tProps = typeof(T).GetTypeInfo().DeclaredProperties
.Where(p => p.SetMethod != null && p.SetMethod.IsPublic && !p.SetMethod.IsStatic)
.ToImmutableDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase);

private readonly CommandService _commands;

public NamedArgumentTypeReader(CommandService commands)
{
_commands = commands;
}

public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
{
var result = new T();
var state = ReadState.LookingForParameter;
int beginRead = 0, currentRead = 0;

while (state != ReadState.End)
{
try
{
var prop = Read(out var arg);
var propVal = await ReadArgumentAsync(prop, arg).ConfigureAwait(false);
if (propVal != null)
prop.SetMethod.Invoke(result, new[] { propVal });
else
return TypeReaderResult.FromError(CommandError.ParseFailed, $"Could not parse the argument for the parameter '{prop.Name}' as type '{prop.PropertyType}'.");
}
catch (Exception ex)
{
//TODO: use the Exception overload after a rebase on latest
return TypeReaderResult.FromError(CommandError.Exception, ex.Message);
}
}

return TypeReaderResult.FromSuccess(result);

PropertyInfo Read(out string arg)
{
string currentParam = null;
char match = '\0';

for (; currentRead < input.Length; currentRead++)
{
var currentChar = input[currentRead];
switch (state)
{
case ReadState.LookingForParameter:
if (Char.IsWhiteSpace(currentChar))
continue;
else
{
beginRead = currentRead;
state = ReadState.InParameter;
}
break;
case ReadState.InParameter:
if (currentChar != ':')
continue;
else
{
currentParam = input.Substring(beginRead, currentRead - beginRead);
state = ReadState.LookingForArgument;
}
break;
case ReadState.LookingForArgument:
if (Char.IsWhiteSpace(currentChar))
continue;
else
{
beginRead = currentRead;
state = (QuotationAliasUtils.GetDefaultAliasMap.TryGetValue(currentChar, out match))
? ReadState.InQuotedArgument
: ReadState.InArgument;
}
break;
case ReadState.InArgument:
if (!Char.IsWhiteSpace(currentChar))
continue;
else
return GetPropAndValue(out arg);
case ReadState.InQuotedArgument:
if (currentChar != match)
continue;
else
return GetPropAndValue(out arg);
}
}

if (currentParam == null)
throw new InvalidOperationException("No parameter name was read.");

return GetPropAndValue(out arg);

PropertyInfo GetPropAndValue(out string argv)
{
bool quoted = state == ReadState.InQuotedArgument;
state = (currentRead == (quoted ? input.Length - 1 : input.Length))
? ReadState.End
: ReadState.LookingForParameter;

if (quoted)
{
argv = input.Substring(beginRead + 1, currentRead - beginRead - 1).Trim();
currentRead++;
}
else
argv = input.Substring(beginRead, currentRead - beginRead);

return _tProps[currentParam];
}
}

async Task<object> ReadArgumentAsync(PropertyInfo prop, string arg)
{
var elemType = prop.PropertyType;
bool isCollection = false;
if (elemType.GetTypeInfo().IsGenericType && elemType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
elemType = prop.PropertyType.GenericTypeArguments[0];
isCollection = true;
}

var overridden = prop.GetCustomAttribute<OverrideTypeReaderAttribute>();
var reader = (overridden != null)
? ModuleClassBuilder.GetTypeReader(_commands, elemType, overridden.TypeReader, services)
: (_commands.GetDefaultTypeReader(elemType)
?? _commands.GetTypeReaders(elemType).FirstOrDefault().Value);

if (reader != null)
{
if (isCollection)
{
var method = _readMultipleMethod.MakeGenericMethod(elemType);
var task = (Task<IEnumerable>)method.Invoke(null, new object[] { reader, context, arg.Split(','), services });
return await task.ConfigureAwait(false);
}
else
return await ReadSingle(reader, context, arg, services).ConfigureAwait(false);
}
return null;
}
}

private static async Task<object> ReadSingle(TypeReader reader, ICommandContext context, string arg, IServiceProvider services)
{
var readResult = await reader.ReadAsync(context, arg, services).ConfigureAwait(false);
return (readResult.IsSuccess)
? readResult.BestMatch
: null;
}
private static async Task<IEnumerable> ReadMultiple<TObj>(TypeReader reader, ICommandContext context, IEnumerable<string> args, IServiceProvider services)
{
var objs = new List<TObj>();
foreach (var arg in args)
{
var read = await ReadSingle(reader, context, arg.Trim(), services).ConfigureAwait(false);
if (read != null)
objs.Add((TObj)read);
}
return objs.ToImmutableArray();
}
private static readonly MethodInfo _readMultipleMethod = typeof(NamedArgumentTypeReader<T>)
.GetTypeInfo()
.DeclaredMethods
.Single(m => m.IsPrivate && m.IsStatic && m.Name == nameof(ReadMultiple));

private enum ReadState
{
LookingForParameter,
InParameter,
LookingForArgument,
InArgument,
InQuotedArgument,
End
}
}
}

+ 12
- 0
src/Discord.Net.Core/Entities/Guilds/IGuild.cs View File

@@ -535,6 +535,18 @@ namespace Discord
/// </returns>
Task<IRole> CreateRoleAsync(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false, RequestOptions options = null);

/// <summary>
/// Adds a user to this guild.
/// </summary>
/// <remarks>
/// This method requires you have an OAuth2 access token for the user, requested with the guilds.join scope, and that the bot have the MANAGE_INVITES permission in the guild.
/// </remarks>
/// <param name="id">The snowflake identifier of the user.</param>
/// <param name="accessToken">The OAuth2 access token for the user, requested with the guilds.join scope.</param>
/// <param name="func">The delegate containing the properties to be applied to the user upon being added to the guild.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>A guild user associated with the specified <paramref name="id" />; <c>null</c> if the user is already in the guild.</returns>
Task<IGuildUser> AddGuildUserAsync(ulong userId, string accessToken, Action<AddGuildUserProperties> func = null, RequestOptions options = null);
/// <summary>
/// Gets a collection of all users in this guild.
/// </summary>


+ 62
- 0
src/Discord.Net.Core/Entities/Users/AddGuildUserProperties.cs View File

@@ -0,0 +1,62 @@
using System.Collections.Generic;

namespace Discord
{
/// <summary>
/// Properties that are used to add a new <see cref="IGuildUser"/> to the guild with the following parameters.
/// </summary>
/// <seealso cref="IGuild.AddGuildUserAsync" />
public class AddGuildUserProperties
{
/// <summary>
/// Gets or sets the user's nickname.
/// </summary>
/// <remarks>
/// To clear the user's nickname, this value can be set to <c>null</c> or
/// <see cref="string.Empty"/>.
/// </remarks>
public Optional<string> Nickname { get; set; }
/// <summary>
/// Gets or sets whether the user should be muted in a voice channel.
/// </summary>
/// <remarks>
/// If this value is set to <c>true</c>, no user will be able to hear this user speak in the guild.
/// </remarks>
public Optional<bool> Mute { get; set; }
/// <summary>
/// Gets or sets whether the user should be deafened in a voice channel.
/// </summary>
/// <remarks>
/// If this value is set to <c>true</c>, this user will not be able to hear anyone speak in the guild.
/// </remarks>
public Optional<bool> Deaf { get; set; }
/// <summary>
/// Gets or sets the roles the user should have.
/// </summary>
/// <remarks>
/// <para>
/// To add a role to a user:
/// <see cref="IGuildUser.AddRolesAsync(IEnumerable{IRole},RequestOptions)" />
/// </para>
/// <para>
/// To remove a role from a user:
/// <see cref="IGuildUser.RemoveRolesAsync(IEnumerable{IRole},RequestOptions)" />
/// </para>
/// </remarks>
public Optional<IEnumerable<IRole>> Roles { get; set; }
/// <summary>
/// Gets or sets the roles the user should have.
/// </summary>
/// <remarks>
/// <para>
/// To add a role to a user:
/// <see cref="IGuildUser.AddRolesAsync(IEnumerable{IRole},RequestOptions)" />
/// </para>
/// <para>
/// To remove a role from a user:
/// <see cref="IGuildUser.RemoveRolesAsync(IEnumerable{IRole},RequestOptions)" />
/// </para>
/// </remarks>
public Optional<IEnumerable<ulong>> RoleIds { get; set; }
}
}

+ 0
- 17
src/Discord.Net.Core/Net/RpcException.cs View File

@@ -1,17 +0,0 @@
using System;

namespace Discord
{
public class RpcException : Exception
{
public int ErrorCode { get; }
public string Reason { get; }

public RpcException(int errorCode, string reason = null)
: base($"The server sent error {errorCode}{(reason != null ? $": \"{reason}\"" : "")}")
{
ErrorCode = errorCode;
Reason = reason;
}
}
}

+ 19
- 0
src/Discord.Net.Rest/API/Rest/AddGuildMemberParams.cs View File

@@ -0,0 +1,19 @@
using Newtonsoft.Json;

namespace Discord.API.Rest
{
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
internal class AddGuildMemberParams
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("nick")]
public Optional<string> Nickname { get; set; }
[JsonProperty("roles")]
public Optional<ulong[]> RoleIds { get; set; }
[JsonProperty("mute")]
public Optional<bool> IsMuted { get; set; }
[JsonProperty("deaf")]
public Optional<bool> IsDeafened { get; set; }
}
}

+ 21
- 7
src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs View File

@@ -1,12 +1,17 @@
#pragma warning disable CS1591
#pragma warning disable CS1591
using System.Collections.Generic;
using System.IO;
using System.Text;
using Discord.Net.Converters;
using Discord.Net.Rest;
using Newtonsoft.Json;

namespace Discord.API.Rest
{
internal class UploadWebhookFileParams
{
private static JsonSerializer _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };

public Stream File { get; }

public Optional<string> Filename { get; set; }
@@ -27,18 +32,27 @@ namespace Discord.API.Rest
var d = new Dictionary<string, object>();
d["file"] = new MultipartFile(File, Filename.GetValueOrDefault("unknown.dat"));

var payload = new Dictionary<string, object>();
if (Content.IsSpecified)
d["content"] = Content.Value;
payload["content"] = Content.Value;
if (IsTTS.IsSpecified)
d["tts"] = IsTTS.Value.ToString();
payload["tts"] = IsTTS.Value.ToString();
if (Nonce.IsSpecified)
d["nonce"] = Nonce.Value;
payload["nonce"] = Nonce.Value;
if (Username.IsSpecified)
d["username"] = Username.Value;
payload["username"] = Username.Value;
if (AvatarUrl.IsSpecified)
d["avatar_url"] = AvatarUrl.Value;
payload["avatar_url"] = AvatarUrl.Value;
if (Embeds.IsSpecified)
d["embeds"] = Embeds.Value;
payload["embeds"] = Embeds.Value;

var json = new StringBuilder();
using (var text = new StringWriter(json))
using (var writer = new JsonTextWriter(text))
_serializer.Serialize(writer, payload);

d["payload_json"] = json.ToString();

return d;
}
}


+ 19
- 0
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -994,6 +994,25 @@ namespace Discord.API
}

//Guild Members
public async Task<GuildMember> AddGuildMemberAsync(ulong guildId, ulong userId, AddGuildMemberParams args, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotEqual(userId, 0, nameof(userId));
Preconditions.NotNull(args, nameof(args));
Preconditions.NotNullOrWhitespace(args.AccessToken, nameof(args.AccessToken));

if (args.RoleIds.IsSpecified)
{
foreach (var roleId in args.RoleIds.Value)
Preconditions.NotEqual(roleId, 0, nameof(roleId));
}

options = RequestOptions.CreateOrClone(options);

var ids = new BucketIds(guildId: guildId);

return await SendJsonAsync<GuildMember>("PUT", () => $"guilds/{guildId}/members/{userId}", args, ids, options: options);
}
public async Task<GuildMember> GetGuildMemberAsync(ulong guildId, ulong userId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));


+ 0
- 110
src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs View File

@@ -1,110 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace Discord.Rest
{
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
internal class RestVirtualMessageChannel : RestEntity<ulong>, IMessageChannel
{
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
public string Mention => MentionUtils.MentionChannel(Id);

internal RestVirtualMessageChannel(BaseDiscordClient discord, ulong id)
: base(discord, id)
{
}
internal static RestVirtualMessageChannel Create(BaseDiscordClient discord, ulong id)
{
return new RestVirtualMessageChannel(discord, id);
}

public Task<RestMessage> GetMessageAsync(ulong id, RequestOptions options = null)
=> ChannelHelper.GetMessageAsync(this, Discord, id, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options);
public IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options);
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null)
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);

public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null)
=> ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options);
public Task<RestUserMessage> SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null)
=> ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options);
public Task<RestUserMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null)

=> ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options);

/// <inheritdoc />
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options);
/// <inheritdoc />
public Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
=> ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options);

/// <inheritdoc />
public Task TriggerTypingAsync(RequestOptions options = null)
=> ChannelHelper.TriggerTypingAsync(this, Discord, options);
/// <inheritdoc />
public IDisposable EnterTypingState(RequestOptions options = null)
=> ChannelHelper.EnterTypingState(this, Discord, options);

private string DebuggerDisplay => $"({Id}, Text)";

//IMessageChannel
async Task<IMessage> IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return await GetMessageAsync(id, options).ConfigureAwait(false);
else
return null;
}
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return GetMessagesAsync(limit, options);
else
return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>();
}
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return GetMessagesAsync(fromMessageId, dir, limit, options);
else
return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>();
}
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return GetMessagesAsync(fromMessage, dir, limit, options);
else
return AsyncEnumerable.Empty<IReadOnlyCollection<IMessage>>();
}
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options)
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false);

async Task<IUserMessage> IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options)
=> await SendFileAsync(filePath, text, isTTS, embed, options);

async Task<IUserMessage> IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options)
=> await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false);
async Task<IUserMessage> IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options)
=> await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false);

//IChannel
string IChannel.Name =>
throw new NotSupportedException();

IAsyncEnumerable<IReadOnlyCollection<IUser>> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) =>
throw new NotSupportedException();

Task<IUser> IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) =>
throw new NotSupportedException();
}
}

+ 28
- 0
src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs View File

@@ -257,6 +257,34 @@ namespace Discord.Rest
}

//Users
public static async Task<RestGuildUser> AddGuildUserAsync(IGuild guild, BaseDiscordClient client, ulong userId, string accessToken,
Action<AddGuildUserProperties> func, RequestOptions options)
{
var args = new AddGuildUserProperties();
func?.Invoke(args);

if (args.Roles.IsSpecified)
{
var ids = args.Roles.Value.Select(r => r.Id);

if (args.RoleIds.IsSpecified)
args.RoleIds.Value.Concat(ids);
else
args.RoleIds = Optional.Create(ids);
}
var apiArgs = new AddGuildMemberParams
{
AccessToken = accessToken,
Nickname = args.Nickname,
IsDeafened = args.Deaf,
IsMuted = args.Mute,
RoleIds = args.RoleIds.IsSpecified ? args.RoleIds.Value.Distinct().ToArray() : Optional.Create<ulong[]>()
};

var model = await client.ApiClient.AddGuildMemberAsync(guild.Id, userId, apiArgs, options);

return model is null ? null : RestGuildUser.Create(client, guild, model);
}
public static async Task<RestGuildUser> GetUserAsync(IGuild guild, BaseDiscordClient client,
ulong id, RequestOptions options)
{


+ 8
- 0
src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs View File

@@ -537,6 +537,10 @@ namespace Discord.Rest
public IAsyncEnumerable<IReadOnlyCollection<RestGuildUser>> GetUsersAsync(RequestOptions options = null)
=> GuildHelper.GetUsersAsync(this, Discord, null, null, options);

/// <inheritdoc />
public Task<RestGuildUser> AddGuildUserAsync(ulong id, string accessToken, Action<AddGuildUserProperties> func = null, RequestOptions options = null)
=> GuildHelper.AddGuildUserAsync(this, Discord, id, accessToken, func, options);

/// <summary>
/// Gets a user from this guild.
/// </summary>
@@ -800,6 +804,10 @@ namespace Discord.Rest
async Task<IRole> IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options)
=> await CreateRoleAsync(name, permissions, color, isHoisted, options).ConfigureAwait(false);

/// <inheritdoc />
async Task<IGuildUser> IGuild.AddGuildUserAsync(ulong userId, string accessToken, Action<AddGuildUserProperties> func, RequestOptions options)
=> await AddGuildUserAsync(userId, accessToken, func, options);

/// <inheritdoc />
async Task<IGuildUser> IGuild.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
{


+ 1
- 1
src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs View File

@@ -1,7 +1,7 @@
using System.Diagnostics;
using Model = Discord.API.GuildEmbed;

namespace Discord
namespace Discord.Rest
{
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public struct RestGuildEmbed


+ 1
- 1
src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs View File

@@ -2,7 +2,7 @@ using Discord.Rest;
using System.Diagnostics;
using Model = Discord.API.VoiceRegion;

namespace Discord
namespace Discord.Rest
{
/// <summary>
/// Represents a REST-based voice region.


+ 1
- 1
src/Discord.Net.Rest/Entities/Users/RestConnection.cs View File

@@ -3,7 +3,7 @@ using System.Collections.Immutable;
using System.Diagnostics;
using Model = Discord.API.Connection;

namespace Discord
namespace Discord.Rest
{
[DebuggerDisplay(@"{DebuggerDisplay,nq}")]
public class RestConnection : IConnection


+ 8
- 0
src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs View File

@@ -669,6 +669,10 @@ namespace Discord.WebSocket
}

//Users
/// <inheritdoc />
public Task<RestGuildUser> AddGuildUserAsync(ulong id, string accessToken, Action<AddGuildUserProperties> func = null, RequestOptions options = null)
=> GuildHelper.AddGuildUserAsync(this, Discord, id, accessToken, func, options);

/// <summary>
/// Gets a user from this guild.
/// </summary>
@@ -1096,6 +1100,10 @@ namespace Discord.WebSocket
/// <inheritdoc />
Task<IReadOnlyCollection<IGuildUser>> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult<IReadOnlyCollection<IGuildUser>>(Users);

/// <inheritdoc />
async Task<IGuildUser> IGuild.AddGuildUserAsync(ulong userId, string accessToken, Action<AddGuildUserProperties> func, RequestOptions options)
=> await AddGuildUserAsync(userId, accessToken, func, options);
/// <inheritdoc />
Task<IGuildUser> IGuild.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult<IGuildUser>(GetUser(id));


+ 4
- 3
test/Discord.Net.Tests/Discord.Net.Tests.csproj View File

@@ -3,6 +3,7 @@
<OutputType>Exe</OutputType>
<RootNamespace>Discord</RootNamespace>
<TargetFramework>netcoreapp1.1</TargetFramework>
<DebugType>portable</DebugType>
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81</PackageTargetFallback>
</PropertyGroup>
<ItemGroup>
@@ -23,8 +24,8 @@
<PackageReference Include="Akavache" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.2" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="xunit.runner.reporters" Version="2.3.1" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="xunit.runner.reporters" Version="2.4.0" />
</ItemGroup>
</Project>

+ 133
- 0
test/Discord.Net.Tests/Tests.TypeReaders.cs View File

@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Discord.Commands;
using Xunit;

namespace Discord
{
public sealed class TypeReaderTests
{
[Fact]
public async Task TestNamedArgumentReader()
{
var commands = new CommandService();
var module = await commands.AddModuleAsync<TestModule>(null);

Assert.NotNull(module);
Assert.NotEmpty(module.Commands);

var cmd = module.Commands[0];
Assert.NotNull(cmd);
Assert.NotEmpty(cmd.Parameters);

var param = cmd.Parameters[0];
Assert.NotNull(param);
Assert.True(param.IsRemainder);

var result = await param.ParseAsync(null, "bar: hello foo: 42");
Assert.True(result.IsSuccess);

var m = result.BestMatch as ArgumentType;
Assert.NotNull(m);
Assert.Equal(expected: 42, actual: m.Foo);
Assert.Equal(expected: "hello", actual: m.Bar);
}

[Fact]
public async Task TestQuotedArgumentValue()
{
var commands = new CommandService();
var module = await commands.AddModuleAsync<TestModule>(null);

Assert.NotNull(module);
Assert.NotEmpty(module.Commands);

var cmd = module.Commands[0];
Assert.NotNull(cmd);
Assert.NotEmpty(cmd.Parameters);

var param = cmd.Parameters[0];
Assert.NotNull(param);
Assert.True(param.IsRemainder);

var result = await param.ParseAsync(null, "foo: 42 bar: 《hello》");
Assert.True(result.IsSuccess);

var m = result.BestMatch as ArgumentType;
Assert.NotNull(m);
Assert.Equal(expected: 42, actual: m.Foo);
Assert.Equal(expected: "hello", actual: m.Bar);
}

[Fact]
public async Task TestNonPatternInput()
{
var commands = new CommandService();
var module = await commands.AddModuleAsync<TestModule>(null);

Assert.NotNull(module);
Assert.NotEmpty(module.Commands);

var cmd = module.Commands[0];
Assert.NotNull(cmd);
Assert.NotEmpty(cmd.Parameters);

var param = cmd.Parameters[0];
Assert.NotNull(param);
Assert.True(param.IsRemainder);

var result = await param.ParseAsync(null, "foobar");
Assert.False(result.IsSuccess);
Assert.Equal(expected: CommandError.Exception, actual: result.Error);
}

[Fact]
public async Task TestMultiple()
{
var commands = new CommandService();
var module = await commands.AddModuleAsync<TestModule>(null);

Assert.NotNull(module);
Assert.NotEmpty(module.Commands);

var cmd = module.Commands[0];
Assert.NotNull(cmd);
Assert.NotEmpty(cmd.Parameters);

var param = cmd.Parameters[0];
Assert.NotNull(param);
Assert.True(param.IsRemainder);

var result = await param.ParseAsync(null, "manyints: \"1, 2, 3, 4, 5, 6, 7\"");
Assert.True(result.IsSuccess);

var m = result.BestMatch as ArgumentType;
Assert.NotNull(m);
Assert.Equal(expected: new int[] { 1, 2, 3, 4, 5, 6, 7 }, actual: m.ManyInts);
}
}

[NamedArgumentType]
public sealed class ArgumentType
{
public int Foo { get; set; }

[OverrideTypeReader(typeof(CustomTypeReader))]
public string Bar { get; set; }

public IEnumerable<int> ManyInts { get; set; }
}

public sealed class CustomTypeReader : TypeReader
{
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
=> Task.FromResult(TypeReaderResult.FromSuccess(input));
}

public sealed class TestModule : ModuleBase
{
[Command("test")]
public Task TestCommand(ArgumentType arg) => Task.Delay(0);
}
}

Loading…
Cancel
Save