@@ -23,7 +23,7 @@ namespace Discord.Commands | |||||
public OverrideTypeReaderAttribute(Type overridenTypeReader) | public OverrideTypeReaderAttribute(Type overridenTypeReader) | ||||
{ | { | ||||
if (!TypeReaderTypeInfo.IsAssignableFrom(overridenTypeReader.GetTypeInfo())) | if (!TypeReaderTypeInfo.IsAssignableFrom(overridenTypeReader.GetTypeInfo())) | ||||
throw new ArgumentException($"{nameof(overridenTypeReader)} must inherit from {nameof(TypeReader)}."); | |||||
throw new ArgumentException($"{nameof(overridenTypeReader)} must inherit from {nameof(TypeReader)}"); | |||||
TypeReader = overridenTypeReader; | TypeReader = overridenTypeReader; | ||||
} | } | ||||
@@ -10,7 +10,7 @@ namespace Discord.Commands | |||||
{ | { | ||||
internal static class ModuleClassBuilder | internal static class ModuleClassBuilder | ||||
{ | { | ||||
private static readonly TypeInfo _moduleTypeInfo = typeof(IModuleBase).GetTypeInfo(); | |||||
private static readonly TypeInfo ModuleTypeInfo = typeof(IModuleBase).GetTypeInfo(); | |||||
public static async Task<IReadOnlyList<TypeInfo>> SearchAsync(Assembly assembly, CommandService service) | public static async Task<IReadOnlyList<TypeInfo>> SearchAsync(Assembly assembly, CommandService service) | ||||
{ | { | ||||
@@ -135,7 +135,7 @@ namespace Discord.Commands | |||||
if (builder.Name == null) | if (builder.Name == null) | ||||
builder.Name = typeInfo.Name; | builder.Name = typeInfo.Name; | ||||
var validCommands = typeInfo.DeclaredMethods.Where(x => IsValidCommandDefinition(x)); | |||||
var validCommands = typeInfo.DeclaredMethods.Where(IsValidCommandDefinition); | |||||
foreach (var method in validCommands) | foreach (var method in validCommands) | ||||
{ | { | ||||
@@ -299,7 +299,7 @@ namespace Discord.Commands | |||||
private static bool IsValidModuleDefinition(TypeInfo typeInfo) | private static bool IsValidModuleDefinition(TypeInfo typeInfo) | ||||
{ | { | ||||
return _moduleTypeInfo.IsAssignableFrom(typeInfo) && | |||||
return ModuleTypeInfo.IsAssignableFrom(typeInfo) && | |||||
!typeInfo.IsAbstract && | !typeInfo.IsAbstract && | ||||
!typeInfo.ContainsGenericParameters; | !typeInfo.ContainsGenericParameters; | ||||
} | } | ||||
@@ -1,4 +1,4 @@ | |||||
using Discord.Commands.Builders; | |||||
using Discord.Commands.Builders; | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
@@ -114,7 +114,7 @@ namespace Discord.Commands | |||||
Attributes = builder.Attributes.ToImmutableArray(); | Attributes = builder.Attributes.ToImmutableArray(); | ||||
Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); | Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); | ||||
HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].IsMultiple : false; | |||||
HasVarArgs = builder.Parameters.Count > 0 && builder.Parameters[builder.Parameters.Count - 1].IsMultiple; | |||||
IgnoreExtraArgs = builder.IgnoreExtraArgs; | IgnoreExtraArgs = builder.IgnoreExtraArgs; | ||||
_action = builder.Callback; | _action = builder.Callback; | ||||
@@ -6,6 +6,7 @@ namespace Discord.Commands | |||||
{ | { | ||||
private readonly CommandService _service; | private readonly CommandService _service; | ||||
private readonly CommandMapNode _root; | private readonly CommandMapNode _root; | ||||
private static readonly string[] BlankAliases = { "" }; | |||||
public CommandMap(CommandService service) | public CommandMap(CommandService service) | ||||
{ | { | ||||
@@ -7,7 +7,7 @@ namespace Discord.Commands | |||||
{ | { | ||||
internal class CommandMapNode | internal class CommandMapNode | ||||
{ | { | ||||
private static readonly char[] WhitespaceChars = new[] { ' ', '\r', '\n' }; | |||||
private static readonly char[] WhitespaceChars = { ' ', '\r', '\n' }; | |||||
private readonly ConcurrentDictionary<string, CommandMapNode> _nodes; | private readonly ConcurrentDictionary<string, CommandMapNode> _nodes; | ||||
private readonly string _name; | private readonly string _name; | ||||
@@ -53,7 +53,6 @@ namespace Discord.Commands | |||||
public void RemoveCommand(CommandService service, string text, int index, CommandInfo command) | public void RemoveCommand(CommandService service, string text, int index, CommandInfo command) | ||||
{ | { | ||||
int nextSegment = NextSegment(text, index, service._separatorChar); | int nextSegment = NextSegment(text, index, service._separatorChar); | ||||
string name; | |||||
lock (_lockObj) | lock (_lockObj) | ||||
{ | { | ||||
@@ -61,13 +60,13 @@ namespace Discord.Commands | |||||
_commands = _commands.Remove(command); | _commands = _commands.Remove(command); | ||||
else | else | ||||
{ | { | ||||
string name; | |||||
if (nextSegment == -1) | if (nextSegment == -1) | ||||
name = text.Substring(index); | name = text.Substring(index); | ||||
else | else | ||||
name = text.Substring(index, nextSegment - index); | name = text.Substring(index, nextSegment - index); | ||||
CommandMapNode nextNode; | |||||
if (_nodes.TryGetValue(name, out nextNode)) | |||||
if (_nodes.TryGetValue(name, out var nextNode)) | |||||
{ | { | ||||
nextNode.RemoveCommand(service, nextSegment == -1 ? "" : text, nextSegment + 1, command); | nextNode.RemoveCommand(service, nextSegment == -1 ? "" : text, nextSegment + 1, command); | ||||
if (nextNode.IsEmpty) | if (nextNode.IsEmpty) | ||||
@@ -8,9 +8,9 @@ namespace Discord.Commands | |||||
internal static class PrimitiveParsers | internal static class PrimitiveParsers | ||||
{ | { | ||||
private static readonly Lazy<IReadOnlyDictionary<Type, Delegate>> _parsers = new Lazy<IReadOnlyDictionary<Type, Delegate>>(CreateParsers); | |||||
private static readonly Lazy<IReadOnlyDictionary<Type, Delegate>> Parsers = new Lazy<IReadOnlyDictionary<Type, Delegate>>(CreateParsers); | |||||
public static IEnumerable<Type> SupportedTypes = _parsers.Value.Keys; | |||||
public static IEnumerable<Type> SupportedTypes = Parsers.Value.Keys; | |||||
static IReadOnlyDictionary<Type, Delegate> CreateParsers() | static IReadOnlyDictionary<Type, Delegate> CreateParsers() | ||||
{ | { | ||||
@@ -34,7 +34,7 @@ namespace Discord.Commands | |||||
return parserBuilder.ToImmutable(); | return parserBuilder.ToImmutable(); | ||||
} | } | ||||
public static TryParseDelegate<T> Get<T>() => (TryParseDelegate<T>)_parsers.Value[typeof(T)]; | |||||
public static Delegate Get(Type type) => _parsers.Value[type]; | |||||
public static TryParseDelegate<T> Get<T>() => (TryParseDelegate<T>)Parsers.Value[typeof(T)]; | |||||
public static Delegate Get(Type type) => Parsers.Value[type]; | |||||
} | } | ||||
} | } |
@@ -6,8 +6,7 @@ namespace Discord.Commands | |||||
{ | { | ||||
internal class TimeSpanTypeReader : TypeReader | internal class TimeSpanTypeReader : TypeReader | ||||
{ | { | ||||
private static readonly string[] _formats = new[] | |||||
{ | |||||
private static readonly string[] Formats = { | |||||
"%d'd'%h'h'%m'm'%s's'", //4d3h2m1s | "%d'd'%h'h'%m'm'%s's'", //4d3h2m1s | ||||
"%d'd'%h'h'%m'm'", //4d3h2m | "%d'd'%h'h'%m'm'", //4d3h2m | ||||
"%d'd'%h'h'%s's'", //4d3h 1s | "%d'd'%h'h'%s's'", //4d3h 1s | ||||
@@ -27,7 +26,7 @@ namespace Discord.Commands | |||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services) | public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services) | ||||
{ | { | ||||
return (TimeSpan.TryParseExact(input.ToLowerInvariant(), _formats, CultureInfo.InvariantCulture, out var timeSpan)) | |||||
return (TimeSpan.TryParseExact(input.ToLowerInvariant(), Formats, CultureInfo.InvariantCulture, out var timeSpan)) | |||||
? Task.FromResult(TypeReaderResult.FromSuccess(timeSpan)) | ? Task.FromResult(TypeReaderResult.FromSuccess(timeSpan)) | ||||
: Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse TimeSpan")); | : Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse TimeSpan")); | ||||
} | } | ||||
@@ -7,7 +7,7 @@ namespace Discord.Commands | |||||
{ | { | ||||
internal static class ReflectionUtils | internal static class ReflectionUtils | ||||
{ | { | ||||
private static readonly TypeInfo _objectTypeInfo = typeof(object).GetTypeInfo(); | |||||
private static readonly TypeInfo ObjectTypeInfo = typeof(object).GetTypeInfo(); | |||||
internal static T CreateObject<T>(TypeInfo typeInfo, CommandService commands, IServiceProvider services = null) | internal static T CreateObject<T>(TypeInfo typeInfo, CommandService commands, IServiceProvider services = null) | ||||
=> CreateBuilder<T>(typeInfo, commands)(services); | => CreateBuilder<T>(typeInfo, commands)(services); | ||||
@@ -52,8 +52,8 @@ namespace Discord.Commands | |||||
} | } | ||||
private static PropertyInfo[] GetProperties(TypeInfo ownerType) | private static PropertyInfo[] GetProperties(TypeInfo ownerType) | ||||
{ | { | ||||
var result = new List<PropertyInfo>(); | |||||
while (ownerType != _objectTypeInfo) | |||||
var result = new List<System.Reflection.PropertyInfo>(); | |||||
while (ownerType != ObjectTypeInfo) | |||||
{ | { | ||||
foreach (var prop in ownerType.DeclaredProperties) | foreach (var prop in ownerType.DeclaredProperties) | ||||
{ | { | ||||
@@ -179,6 +179,17 @@ namespace Discord | |||||
(uint)(b * 255.0f); | (uint)(b * 255.0f); | ||||
} | } | ||||
public static bool operator ==(Color lhs, Color rhs) | |||||
=> lhs.RawValue == rhs.RawValue; | |||||
public static bool operator !=(Color lhs, Color rhs) | |||||
=> lhs.RawValue != rhs.RawValue; | |||||
public override bool Equals(object obj) | |||||
=> (obj is Color c && RawValue == c.RawValue); | |||||
public override int GetHashCode() => RawValue.GetHashCode(); | |||||
#if NETSTANDARD2_0 || NET45 | #if NETSTANDARD2_0 || NET45 | ||||
public static implicit operator StandardColor(Color color) => | public static implicit operator StandardColor(Color color) => | ||||
StandardColor.FromArgb((int)color.RawValue); | StandardColor.FromArgb((int)color.RawValue); | ||||
@@ -16,14 +16,14 @@ namespace Discord | |||||
=> await client.GetPrivateChannelAsync(id).ConfigureAwait(false) as IDMChannel; | => await client.GetPrivateChannelAsync(id).ConfigureAwait(false) as IDMChannel; | ||||
/// <summary> Gets all available DM channels for the client. </summary> | /// <summary> Gets all available DM channels for the client. </summary> | ||||
public static async Task<IEnumerable<IDMChannel>> GetDMChannelsAsync(this IDiscordClient client) | public static async Task<IEnumerable<IDMChannel>> GetDMChannelsAsync(this IDiscordClient client) | ||||
=> (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).Select(x => x as IDMChannel).Where(x => x != null); | |||||
=> (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).OfType<IDMChannel>(); | |||||
/// <summary> Gets the group channel with the provided ID. </summary> | /// <summary> Gets the group channel with the provided ID. </summary> | ||||
public static async Task<IGroupChannel> GetGroupChannelAsync(this IDiscordClient client, ulong id) | public static async Task<IGroupChannel> GetGroupChannelAsync(this IDiscordClient client, ulong id) | ||||
=> await client.GetPrivateChannelAsync(id).ConfigureAwait(false) as IGroupChannel; | => await client.GetPrivateChannelAsync(id).ConfigureAwait(false) as IGroupChannel; | ||||
/// <summary> Gets all available group channels for the client. </summary> | /// <summary> Gets all available group channels for the client. </summary> | ||||
public static async Task<IEnumerable<IGroupChannel>> GetGroupChannelsAsync(this IDiscordClient client) | public static async Task<IEnumerable<IGroupChannel>> GetGroupChannelsAsync(this IDiscordClient client) | ||||
=> (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).Select(x => x as IGroupChannel).Where(x => x != null); | |||||
=> (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).OfType<IGroupChannel>(); | |||||
/// <summary> Gets the most optimal voice region for the client. </summary> | /// <summary> Gets the most optimal voice region for the client. </summary> | ||||
public static async Task<IVoiceRegion> GetOptimalVoiceRegionAsync(this IDiscordClient discord) | public static async Task<IVoiceRegion> GetOptimalVoiceRegionAsync(this IDiscordClient discord) | ||||
@@ -1,4 +1,4 @@ | |||||
using System; | |||||
using System; | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
@@ -60,7 +60,7 @@ namespace Discord | |||||
if (Current.Count == 0) | if (Current.Count == 0) | ||||
_info.Remaining = 0; | _info.Remaining = 0; | ||||
} | } | ||||
_info.PageSize = _info.Remaining != null ? (int)Math.Min(_info.Remaining.Value, _source.PageSize) : _source.PageSize; | |||||
_info.PageSize = _info.Remaining != null ? Math.Min(_info.Remaining.Value, _source.PageSize) : _source.PageSize; | |||||
if (_info.Remaining != 0) | if (_info.Remaining != 0) | ||||
{ | { | ||||
@@ -74,4 +74,4 @@ namespace Discord | |||||
public void Dispose() { Current = null; } | public void Dispose() { Current = null; } | ||||
} | } | ||||
} | } | ||||
} | |||||
} |
@@ -119,13 +119,11 @@ namespace Discord | |||||
resolvedPermissions = mask; //Owners and administrators always have all permissions | resolvedPermissions = mask; //Owners and administrators always have all permissions | ||||
else | else | ||||
{ | { | ||||
OverwritePermissions? perms; | |||||
//Start with this user's guild permissions | //Start with this user's guild permissions | ||||
resolvedPermissions = guildPermissions; | resolvedPermissions = guildPermissions; | ||||
//Give/Take Everyone permissions | //Give/Take Everyone permissions | ||||
perms = channel.GetPermissionOverwrite(guild.EveryoneRole); | |||||
var perms = channel.GetPermissionOverwrite(guild.EveryoneRole); | |||||
if (perms != null) | if (perms != null) | ||||
resolvedPermissions = (resolvedPermissions & ~perms.Value.DenyValue) | perms.Value.AllowValue; | resolvedPermissions = (resolvedPermissions & ~perms.Value.DenyValue) | perms.Value.AllowValue; | ||||
@@ -133,7 +131,7 @@ namespace Discord | |||||
ulong deniedPermissions = 0UL, allowedPermissions = 0UL; | ulong deniedPermissions = 0UL, allowedPermissions = 0UL; | ||||
foreach (var roleId in user.RoleIds) | foreach (var roleId in user.RoleIds) | ||||
{ | { | ||||
IRole role = null; | |||||
IRole role; | |||||
if (roleId != guild.EveryoneRole.Id && (role = guild.GetRole(roleId)) != null) | if (roleId != guild.EveryoneRole.Id && (role = guild.GetRole(roleId)) != null) | ||||
{ | { | ||||
perms = channel.GetPermissionOverwrite(role); | perms = channel.GetPermissionOverwrite(role); | ||||
@@ -0,0 +1,46 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
public static class TokenUtils | |||||
{ | |||||
/// <summary> | |||||
/// Checks the validity of the supplied token of a specific type. | |||||
/// </summary> | |||||
/// <param name="tokenType"> The type of token to validate. </param> | |||||
/// <param name="token"> The token value to validate. </param> | |||||
/// <exception cref="ArgumentNullException"> Thrown when the supplied token string is null, empty, or contains only whitespace.</exception> | |||||
/// <exception cref="ArgumentException"> Thrown when the supplied TokenType or token value is invalid. </exception> | |||||
public static void ValidateToken(TokenType tokenType, string token) | |||||
{ | |||||
// A Null or WhiteSpace token of any type is invalid. | |||||
if (string.IsNullOrWhiteSpace(token)) | |||||
throw new ArgumentNullException("A token cannot be null, empty, or contain only whitespace.", nameof(token)); | |||||
switch (tokenType) | |||||
{ | |||||
case TokenType.Webhook: | |||||
// no validation is performed on Webhook tokens | |||||
break; | |||||
case TokenType.Bearer: | |||||
// no validation is performed on Bearer tokens | |||||
break; | |||||
case TokenType.Bot: | |||||
// bot tokens are assumed to be at least 59 characters in length | |||||
// this value was determined by referencing examples in the discord documentation, and by comparing with | |||||
// pre-existing tokens | |||||
if (token.Length < 59) | |||||
throw new ArgumentException("A Bot token must be at least 59 characters in length.", nameof(token)); | |||||
break; | |||||
default: | |||||
// All unrecognized TokenTypes (including User tokens) are considered to be invalid. | |||||
throw new ArgumentException("Unrecognized TokenType.", nameof(token)); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -55,17 +55,17 @@ namespace Discord.Rest | |||||
}; | }; | ||||
ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); | ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); | ||||
} | } | ||||
public async Task LoginAsync(TokenType tokenType, string token) | |||||
public async Task LoginAsync(TokenType tokenType, string token, bool validateToken = true) | |||||
{ | { | ||||
await _stateLock.WaitAsync().ConfigureAwait(false); | await _stateLock.WaitAsync().ConfigureAwait(false); | ||||
try | try | ||||
{ | { | ||||
await LoginInternalAsync(tokenType, token).ConfigureAwait(false); | |||||
await LoginInternalAsync(tokenType, token, validateToken).ConfigureAwait(false); | |||||
} | } | ||||
finally { _stateLock.Release(); } | finally { _stateLock.Release(); } | ||||
} | } | ||||
private async Task LoginInternalAsync(TokenType tokenType, string token) | |||||
private async Task LoginInternalAsync(TokenType tokenType, string token, bool validateToken) | |||||
{ | { | ||||
if (_isFirstLogin) | if (_isFirstLogin) | ||||
{ | { | ||||
@@ -79,6 +79,21 @@ namespace Discord.Rest | |||||
try | try | ||||
{ | { | ||||
// If token validation is enabled, validate the token and let it throw any ArgumentExceptions | |||||
// that result from invalid parameters | |||||
if (validateToken) | |||||
{ | |||||
try | |||||
{ | |||||
TokenUtils.ValidateToken(tokenType, token); | |||||
} | |||||
catch (ArgumentException ex) | |||||
{ | |||||
// log these ArgumentExceptions and allow for the client to attempt to log in anyways | |||||
await LogManager.WarningAsync("Discord", "A supplied token was invalid.", ex).ConfigureAwait(false); | |||||
} | |||||
} | |||||
await ApiClient.LoginAsync(tokenType, token).ConfigureAwait(false); | await ApiClient.LoginAsync(tokenType, token).ConfigureAwait(false); | ||||
await OnLoginAsync(tokenType, token).ConfigureAwait(false); | await OnLoginAsync(tokenType, token).ConfigureAwait(false); | ||||
LoginState = LoginState.LoggedIn; | LoginState = LoginState.LoggedIn; | ||||
@@ -1407,9 +1407,9 @@ namespace Discord.API | |||||
int argId = int.Parse(format.Substring(leftIndex + 1, rightIndex - leftIndex - 1)); | int argId = int.Parse(format.Substring(leftIndex + 1, rightIndex - leftIndex - 1)); | ||||
string fieldName = GetFieldName(methodArgs[argId + 1]); | string fieldName = GetFieldName(methodArgs[argId + 1]); | ||||
int? mappedId; | |||||
mappedId = BucketIds.GetIndex(fieldName); | |||||
var mappedId = BucketIds.GetIndex(fieldName); | |||||
if(!mappedId.HasValue && rightIndex != endIndex && format.Length > rightIndex + 1 && format[rightIndex + 1] == '/') //Ignore the next slash | if(!mappedId.HasValue && rightIndex != endIndex && format.Length > rightIndex + 1 && format[rightIndex + 1] == '/') //Ignore the next slash | ||||
rightIndex++; | rightIndex++; | ||||
@@ -277,7 +277,7 @@ namespace Discord.Rest | |||||
public async Task<IReadOnlyCollection<RestTextChannel>> GetTextChannelsAsync(RequestOptions options = null) | public async Task<IReadOnlyCollection<RestTextChannel>> GetTextChannelsAsync(RequestOptions options = null) | ||||
{ | { | ||||
var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); | var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); | ||||
return channels.Select(x => x as RestTextChannel).Where(x => x != null).ToImmutableArray(); | |||||
return channels.OfType<RestTextChannel>().ToImmutableArray(); | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
@@ -306,7 +306,7 @@ namespace Discord.Rest | |||||
public async Task<IReadOnlyCollection<RestVoiceChannel>> GetVoiceChannelsAsync(RequestOptions options = null) | public async Task<IReadOnlyCollection<RestVoiceChannel>> GetVoiceChannelsAsync(RequestOptions options = null) | ||||
{ | { | ||||
var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); | var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); | ||||
return channels.Select(x => x as RestVoiceChannel).Where(x => x != null).ToImmutableArray(); | |||||
return channels.OfType<RestVoiceChannel>().ToImmutableArray(); | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
@@ -320,7 +320,7 @@ namespace Discord.Rest | |||||
public async Task<IReadOnlyCollection<RestCategoryChannel>> GetCategoryChannelsAsync(RequestOptions options = null) | public async Task<IReadOnlyCollection<RestCategoryChannel>> GetCategoryChannelsAsync(RequestOptions options = null) | ||||
{ | { | ||||
var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); | var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); | ||||
return channels.Select(x => x as RestCategoryChannel).Where(x => x != null).ToImmutableArray(); | |||||
return channels.OfType<RestCategoryChannel>().ToImmutableArray(); | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
@@ -133,14 +133,14 @@ namespace Discord.Net.Rest | |||||
return new RestResponse(response.StatusCode, headers, stream); | return new RestResponse(response.StatusCode, headers, stream); | ||||
} | } | ||||
private static readonly HttpMethod _patch = new HttpMethod("PATCH"); | |||||
private static readonly HttpMethod Patch = new HttpMethod("PATCH"); | |||||
private HttpMethod GetMethod(string method) | private HttpMethod GetMethod(string method) | ||||
{ | { | ||||
switch (method) | switch (method) | ||||
{ | { | ||||
case "DELETE": return HttpMethod.Delete; | case "DELETE": return HttpMethod.Delete; | ||||
case "GET": return HttpMethod.Get; | case "GET": return HttpMethod.Get; | ||||
case "PATCH": return _patch; | |||||
case "PATCH": return Patch; | |||||
case "POST": return HttpMethod.Post; | case "POST": return HttpMethod.Post; | ||||
case "PUT": return HttpMethod.Put; | case "PUT": return HttpMethod.Put; | ||||
default: throw new ArgumentOutOfRangeException(nameof(method), $"Unknown HttpMethod: {method}"); | default: throw new ArgumentOutOfRangeException(nameof(method), $"Unknown HttpMethod: {method}"); | ||||
@@ -1,4 +1,4 @@ | |||||
using System.Collections.Immutable; | |||||
using System.Collections.Immutable; | |||||
namespace Discord.Net.Queue | namespace Discord.Net.Queue | ||||
{ | { | ||||
@@ -9,8 +9,8 @@ namespace Discord.Net.Queue | |||||
} | } | ||||
internal struct ClientBucket | internal struct ClientBucket | ||||
{ | { | ||||
private static readonly ImmutableDictionary<ClientBucketType, ClientBucket> _defsByType; | |||||
private static readonly ImmutableDictionary<string, ClientBucket> _defsById; | |||||
private static readonly ImmutableDictionary<ClientBucketType, ClientBucket> DefsByType; | |||||
private static readonly ImmutableDictionary<string, ClientBucket> DefsById; | |||||
static ClientBucket() | static ClientBucket() | ||||
{ | { | ||||
@@ -23,16 +23,16 @@ namespace Discord.Net.Queue | |||||
var builder = ImmutableDictionary.CreateBuilder<ClientBucketType, ClientBucket>(); | var builder = ImmutableDictionary.CreateBuilder<ClientBucketType, ClientBucket>(); | ||||
foreach (var bucket in buckets) | foreach (var bucket in buckets) | ||||
builder.Add(bucket.Type, bucket); | builder.Add(bucket.Type, bucket); | ||||
_defsByType = builder.ToImmutable(); | |||||
DefsByType = builder.ToImmutable(); | |||||
var builder2 = ImmutableDictionary.CreateBuilder<string, ClientBucket>(); | var builder2 = ImmutableDictionary.CreateBuilder<string, ClientBucket>(); | ||||
foreach (var bucket in buckets) | foreach (var bucket in buckets) | ||||
builder2.Add(bucket.Id, bucket); | builder2.Add(bucket.Id, bucket); | ||||
_defsById = builder2.ToImmutable(); | |||||
DefsById = builder2.ToImmutable(); | |||||
} | } | ||||
public static ClientBucket Get(ClientBucketType type) => _defsByType[type]; | |||||
public static ClientBucket Get(string id) => _defsById[id]; | |||||
public static ClientBucket Get(ClientBucketType type) => DefsByType[type]; | |||||
public static ClientBucket Get(string id) => DefsById[id]; | |||||
public ClientBucketType Type { get; } | public ClientBucketType Type { get; } | ||||
public string Id { get; } | public string Id { get; } | ||||
@@ -16,10 +16,10 @@ namespace Discord.Net.Queue | |||||
private readonly ConcurrentDictionary<string, RequestBucket> _buckets; | private readonly ConcurrentDictionary<string, RequestBucket> _buckets; | ||||
private readonly SemaphoreSlim _tokenLock; | private readonly SemaphoreSlim _tokenLock; | ||||
private readonly CancellationTokenSource _cancelToken; //Dispose token | |||||
private CancellationTokenSource _clearToken; | private CancellationTokenSource _clearToken; | ||||
private CancellationToken _parentToken; | private CancellationToken _parentToken; | ||||
private CancellationToken _requestCancelToken; //Parent token + Clear token | private CancellationToken _requestCancelToken; //Parent token + Clear token | ||||
private CancellationTokenSource _cancelToken; //Dispose token | |||||
private DateTimeOffset _waitUntil; | private DateTimeOffset _waitUntil; | ||||
private Task _cleanupTask; | private Task _cleanupTask; | ||||
@@ -331,7 +331,7 @@ namespace Discord.Audio | |||||
{ | { | ||||
if (pair.Key == value) | if (pair.Key == value) | ||||
{ | { | ||||
int latency = (int)(Environment.TickCount - pair.Value); | |||||
int latency = Environment.TickCount - pair.Value; | |||||
int before = UdpLatency; | int before = UdpLatency; | ||||
UdpLatency = latency; | UdpLatency = latency; | ||||
@@ -72,7 +72,7 @@ namespace Discord.WebSocket | |||||
switch (channel) | switch (channel) | ||||
{ | { | ||||
case SocketDMChannel dmChannel: | case SocketDMChannel dmChannel: | ||||
_dmChannels.TryRemove(dmChannel.Recipient.Id, out var _); | |||||
_dmChannels.TryRemove(dmChannel.Recipient.Id, out _); | |||||
break; | break; | ||||
case SocketGroupChannel _: | case SocketGroupChannel _: | ||||
_groupChannels.TryRemove(id); | _groupChannels.TryRemove(id); | ||||
@@ -13,11 +13,11 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
private readonly DiscordSocketConfig _baseConfig; | private readonly DiscordSocketConfig _baseConfig; | ||||
private readonly SemaphoreSlim _connectionGroupLock; | private readonly SemaphoreSlim _connectionGroupLock; | ||||
private int[] _shardIds; | |||||
private readonly Dictionary<int, int> _shardIdsToIndex; | private readonly Dictionary<int, int> _shardIdsToIndex; | ||||
private readonly bool _automaticShards; | |||||
private int[] _shardIds; | |||||
private DiscordSocketClient[] _shards; | private DiscordSocketClient[] _shards; | ||||
private int _totalShards; | private int _totalShards; | ||||
private readonly bool _automaticShards; | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override int Latency { get => GetLatency(); protected set { } } | public override int Latency { get => GetLatency(); protected set { } } | ||||
@@ -26,7 +26,7 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override IActivity Activity { get => _shards[0].Activity; protected set { } } | public override IActivity Activity { get => _shards[0].Activity; protected set { } } | ||||
internal new DiscordSocketApiClient ApiClient => base.ApiClient; | |||||
internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(GetGuildCount); | public override IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(GetGuildCount); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
@@ -25,9 +25,9 @@ namespace Discord.API | |||||
public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } | public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } | ||||
private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>(); | private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>(); | ||||
private readonly bool _isExplicitUrl; | |||||
private CancellationTokenSource _connectCancelToken; | private CancellationTokenSource _connectCancelToken; | ||||
private string _gatewayUrl; | private string _gatewayUrl; | ||||
private bool _isExplicitUrl; | |||||
//Store our decompression streams for zlib shared state | //Store our decompression streams for zlib shared state | ||||
private MemoryStream _compressed; | private MemoryStream _compressed; | ||||
@@ -83,7 +83,7 @@ namespace Discord.WebSocket | |||||
/// An collection of DM channels that have been opened in this session. | /// An collection of DM channels that have been opened in this session. | ||||
/// </returns> | /// </returns> | ||||
public IReadOnlyCollection<SocketDMChannel> DMChannels | public IReadOnlyCollection<SocketDMChannel> DMChannels | ||||
=> State.PrivateChannels.Select(x => x as SocketDMChannel).Where(x => x != null).ToImmutableArray(); | |||||
=> State.PrivateChannels.OfType<SocketDMChannel>().ToImmutableArray(); | |||||
/// <summary> | /// <summary> | ||||
/// Gets a collection of group channels opened in this session. | /// Gets a collection of group channels opened in this session. | ||||
/// </summary> | /// </summary> | ||||
@@ -98,7 +98,7 @@ namespace Discord.WebSocket | |||||
/// An collection of group channels that have been opened in this session. | /// An collection of group channels that have been opened in this session. | ||||
/// </returns> | /// </returns> | ||||
public IReadOnlyCollection<SocketGroupChannel> GroupChannels | public IReadOnlyCollection<SocketGroupChannel> GroupChannels | ||||
=> State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); | |||||
=> State.PrivateChannels.OfType<SocketGroupChannel>().ToImmutableArray(); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection(); | public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection(); | ||||
@@ -1747,7 +1747,7 @@ namespace Discord.WebSocket | |||||
if (eventHandler.HasSubscribers) | if (eventHandler.HasSubscribers) | ||||
{ | { | ||||
if (HandlerTimeout.HasValue) | if (HandlerTimeout.HasValue) | ||||
await TimeoutWrap(name, () => eventHandler.InvokeAsync()).ConfigureAwait(false); | |||||
await TimeoutWrap(name, eventHandler.InvokeAsync).ConfigureAwait(false); | |||||
else | else | ||||
await eventHandler.InvokeAsync().ConfigureAwait(false); | await eventHandler.InvokeAsync().ConfigureAwait(false); | ||||
} | } | ||||
@@ -39,8 +39,8 @@ namespace Discord.Audio | |||||
private readonly JsonSerializer _serializer; | private readonly JsonSerializer _serializer; | ||||
private readonly SemaphoreSlim _connectionLock; | private readonly SemaphoreSlim _connectionLock; | ||||
private readonly IUdpSocket _udp; | |||||
private CancellationTokenSource _connectCancelToken; | private CancellationTokenSource _connectCancelToken; | ||||
private IUdpSocket _udp; | |||||
private bool _isDisposed; | private bool _isDisposed; | ||||
private ulong _nextKeepalive; | private ulong _nextKeepalive; | ||||
@@ -21,10 +21,10 @@ namespace Discord.WebSocket | |||||
public class SocketGroupChannel : SocketChannel, IGroupChannel, ISocketPrivateChannel, ISocketMessageChannel, ISocketAudioChannel | public class SocketGroupChannel : SocketChannel, IGroupChannel, ISocketPrivateChannel, ISocketMessageChannel, ISocketAudioChannel | ||||
{ | { | ||||
private readonly MessageCache _messages; | private readonly MessageCache _messages; | ||||
private readonly ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates; | |||||
private string _iconId; | private string _iconId; | ||||
private ConcurrentDictionary<ulong, SocketGroupUser> _users; | private ConcurrentDictionary<ulong, SocketGroupUser> _users; | ||||
private readonly ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates; | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public string Name { get; private set; } | public string Name { get; private set; } | ||||
@@ -153,7 +153,7 @@ namespace Discord.WebSocket | |||||
internal SocketGroupUser GetOrAddUser(UserModel model) | internal SocketGroupUser GetOrAddUser(UserModel model) | ||||
{ | { | ||||
if (_users.TryGetValue(model.Id, out SocketGroupUser user)) | if (_users.TryGetValue(model.Id, out SocketGroupUser user)) | ||||
return user as SocketGroupUser; | |||||
return user; | |||||
else | else | ||||
{ | { | ||||
var privateUser = SocketGroupUser.Create(this, Discord.State, model); | var privateUser = SocketGroupUser.Create(this, Discord.State, model); | ||||
@@ -167,7 +167,7 @@ namespace Discord.WebSocket | |||||
if (_users.TryRemove(id, out SocketGroupUser user)) | if (_users.TryRemove(id, out SocketGroupUser user)) | ||||
{ | { | ||||
user.GlobalUser.RemoveRef(Discord); | user.GlobalUser.RemoveRef(Discord); | ||||
return user as SocketGroupUser; | |||||
return user; | |||||
} | } | ||||
return null; | return null; | ||||
} | } | ||||
@@ -59,7 +59,7 @@ namespace Discord.WebSocket | |||||
=> ChannelHelper.ModifyAsync(this, Discord, func, options); | => ChannelHelper.ModifyAsync(this, Discord, func, options); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public async Task<IAudioClient> ConnectAsync(bool selfDeaf, bool selfMute, bool external) | |||||
public async Task<IAudioClient> ConnectAsync(bool selfDeaf = false, bool selfMute = false, bool external = false) | |||||
{ | { | ||||
return await Guild.ConnectAudioAsync(Id, selfDeaf, selfMute, external).ConfigureAwait(false); | return await Guild.ConnectAudioAsync(Id, selfDeaf, selfMute, external).ConfigureAwait(false); | ||||
} | } | ||||
@@ -171,7 +171,7 @@ namespace Discord.WebSocket | |||||
/// A read-only collection of message channels found within this guild. | /// A read-only collection of message channels found within this guild. | ||||
/// </returns> | /// </returns> | ||||
public IReadOnlyCollection<SocketTextChannel> TextChannels | public IReadOnlyCollection<SocketTextChannel> TextChannels | ||||
=> Channels.Select(x => x as SocketTextChannel).Where(x => x != null).ToImmutableArray(); | |||||
=> Channels.OfType<SocketTextChannel>().ToImmutableArray(); | |||||
/// <summary> | /// <summary> | ||||
/// Gets a collection of all voice channels in this guild. | /// Gets a collection of all voice channels in this guild. | ||||
/// </summary> | /// </summary> | ||||
@@ -179,7 +179,7 @@ namespace Discord.WebSocket | |||||
/// A read-only collection of voice channels found within this guild. | /// A read-only collection of voice channels found within this guild. | ||||
/// </returns> | /// </returns> | ||||
public IReadOnlyCollection<SocketVoiceChannel> VoiceChannels | public IReadOnlyCollection<SocketVoiceChannel> VoiceChannels | ||||
=> Channels.Select(x => x as SocketVoiceChannel).Where(x => x != null).ToImmutableArray(); | |||||
=> Channels.OfType<SocketVoiceChannel>().ToImmutableArray(); | |||||
/// <summary> | /// <summary> | ||||
/// Gets a collection of all category channels in this guild. | /// Gets a collection of all category channels in this guild. | ||||
/// </summary> | /// </summary> | ||||
@@ -187,7 +187,7 @@ namespace Discord.WebSocket | |||||
/// A read-only collection of category channels found within this guild. | /// A read-only collection of category channels found within this guild. | ||||
/// </returns> | /// </returns> | ||||
public IReadOnlyCollection<SocketCategoryChannel> CategoryChannels | public IReadOnlyCollection<SocketCategoryChannel> CategoryChannels | ||||
=> Channels.Select(x => x as SocketCategoryChannel).Where(x => x != null).ToImmutableArray(); | |||||
=> Channels.OfType<SocketCategoryChannel>().ToImmutableArray(); | |||||
/// <summary> | /// <summary> | ||||
/// Gets the current logged-in user. | /// Gets the current logged-in user. | ||||
/// </summary> | /// </summary> | ||||
@@ -870,7 +870,7 @@ namespace Discord.WebSocket | |||||
await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); | await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); | ||||
} | } | ||||
catch (Exception) | |||||
catch | |||||
{ | { | ||||
await DisconnectAudioInternalAsync().ConfigureAwait(false); | await DisconnectAudioInternalAsync().ConfigureAwait(false); | ||||
throw; | throw; | ||||
@@ -887,7 +887,7 @@ namespace Discord.WebSocket | |||||
throw new TimeoutException(); | throw new TimeoutException(); | ||||
return await promise.Task.ConfigureAwait(false); | return await promise.Task.ConfigureAwait(false); | ||||
} | } | ||||
catch (Exception) | |||||
catch | |||||
{ | { | ||||
await DisconnectAudioAsync().ConfigureAwait(false); | await DisconnectAudioAsync().ConfigureAwait(false); | ||||
throw; | throw; | ||||
@@ -28,7 +28,7 @@ namespace Discord.WebSocket | |||||
_orderedMessages.Enqueue(message.Id); | _orderedMessages.Enqueue(message.Id); | ||||
while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out ulong msgId)) | while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out ulong msgId)) | ||||
_messages.TryRemove(msgId, out SocketMessage _); | |||||
_messages.TryRemove(msgId, out _); | |||||
} | } | ||||
} | } | ||||
@@ -15,13 +15,13 @@ namespace Discord.WebSocket | |||||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
public class SocketUserMessage : SocketMessage, IUserMessage | public class SocketUserMessage : SocketMessage, IUserMessage | ||||
{ | { | ||||
private readonly List<SocketReaction> _reactions = new List<SocketReaction>(); | |||||
private bool _isMentioningEveryone, _isTTS, _isPinned; | private bool _isMentioningEveryone, _isTTS, _isPinned; | ||||
private long? _editedTimestampTicks; | private long? _editedTimestampTicks; | ||||
private ImmutableArray<Attachment> _attachments; | private ImmutableArray<Attachment> _attachments; | ||||
private ImmutableArray<Embed> _embeds; | private ImmutableArray<Embed> _embeds; | ||||
private ImmutableArray<ITag> _tags; | private ImmutableArray<ITag> _tags; | ||||
private readonly List<SocketReaction> _reactions = new List<SocketReaction>(); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override bool IsTTS => _isTTS; | public override bool IsTTS => _isTTS; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
@@ -22,8 +22,8 @@ namespace Discord.Net.WebSockets | |||||
private readonly SemaphoreSlim _lock; | private readonly SemaphoreSlim _lock; | ||||
private readonly Dictionary<string, string> _headers; | private readonly Dictionary<string, string> _headers; | ||||
private readonly IWebProxy _proxy; | |||||
private ClientWebSocket _client; | private ClientWebSocket _client; | ||||
private IWebProxy _proxy; | |||||
private Task _task; | private Task _task; | ||||
private CancellationTokenSource _cancelTokenSource; | private CancellationTokenSource _cancelTokenSource; | ||||
private CancellationToken _cancelToken, _parentToken; | private CancellationToken _cancelToken, _parentToken; | ||||
@@ -0,0 +1,124 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
using Xunit; | |||||
namespace Discord | |||||
{ | |||||
public class TokenUtilsTests | |||||
{ | |||||
/// <summary> | |||||
/// Tests the usage of <see cref="TokenUtils.ValidateToken(TokenType, string)"/> | |||||
/// to see that when a null, empty or whitespace-only string is passed as the token, | |||||
/// it will throw an ArgumentNullException. | |||||
/// </summary> | |||||
[Theory] | |||||
[InlineData(null)] | |||||
[InlineData("")] // string.Empty isn't a constant type | |||||
[InlineData(" ")] | |||||
[InlineData(" ")] | |||||
[InlineData("\t")] | |||||
public void TestNullOrWhitespaceToken(string token) | |||||
{ | |||||
// an ArgumentNullException should be thrown, regardless of the TokenType | |||||
Assert.Throws<ArgumentNullException>(() => TokenUtils.ValidateToken(TokenType.Bearer, token)); | |||||
Assert.Throws<ArgumentNullException>(() => TokenUtils.ValidateToken(TokenType.Bot, token)); | |||||
Assert.Throws<ArgumentNullException>(() => TokenUtils.ValidateToken(TokenType.Webhook, token)); | |||||
} | |||||
/// <summary> | |||||
/// Tests the behavior of <see cref="TokenUtils.ValidateToken(TokenType, string)"/> | |||||
/// to see that valid Webhook tokens do not throw Exceptions. | |||||
/// </summary> | |||||
/// <param name="token"></param> | |||||
[Theory] | |||||
[InlineData("123123123")] | |||||
// bot token | |||||
[InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")] | |||||
// bearer token taken from discord docs | |||||
[InlineData("6qrZcUqja7812RVdnEKjpzOL4CvHBFG")] | |||||
// client secret | |||||
[InlineData("937it3ow87i4ery69876wqire")] | |||||
public void TestWebhookTokenDoesNotThrowExceptions(string token) | |||||
{ | |||||
TokenUtils.ValidateToken(TokenType.Webhook, token); | |||||
} | |||||
// No tests for invalid webhook token behavior, because there is nothing there yet. | |||||
/// <summary> | |||||
/// Tests the behavior of <see cref="TokenUtils.ValidateToken(TokenType, string)"/> | |||||
/// to see that valid Webhook tokens do not throw Exceptions. | |||||
/// </summary> | |||||
/// <param name="token"></param> | |||||
[Theory] | |||||
[InlineData("123123123")] | |||||
// bot token | |||||
[InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")] | |||||
// bearer token taken from discord docs | |||||
[InlineData("6qrZcUqja7812RVdnEKjpzOL4CvHBFG")] | |||||
// client secret | |||||
[InlineData("937it3ow87i4ery69876wqire")] | |||||
public void TestBearerTokenDoesNotThrowExceptions(string token) | |||||
{ | |||||
TokenUtils.ValidateToken(TokenType.Bearer, token); | |||||
} | |||||
// No tests for invalid bearer token behavior, because there is nothing there yet. | |||||
/// <summary> | |||||
/// Tests the behavior of <see cref="TokenUtils.ValidateToken(TokenType, string)"/> | |||||
/// to see that valid Bot tokens do not throw Exceptions. | |||||
/// Valid Bot tokens can be strings of length 59 or above. | |||||
/// </summary> | |||||
[Theory] | |||||
[InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")] | |||||
[InlineData("This appears to be completely invalid, however the current validation rules are not very strict.")] | |||||
[InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWss")] | |||||
[InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWsMTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")] | |||||
public void TestBotTokenDoesNotThrowExceptions(string token) | |||||
{ | |||||
// This example token is pulled from the Discord Docs | |||||
// https://discordapp.com/developers/docs/reference#authentication-example-bot-token-authorization-header | |||||
// should not throw any exception | |||||
TokenUtils.ValidateToken(TokenType.Bot, token); | |||||
} | |||||
/// <summary> | |||||
/// Tests the usage of <see cref="TokenUtils.ValidateToken(TokenType, string)"/> with | |||||
/// a Bot token that is invalid. | |||||
/// </summary> | |||||
[Theory] | |||||
[InlineData("This is invalid")] | |||||
// missing a single character from the end | |||||
[InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKW")] | |||||
// bearer token | |||||
[InlineData("6qrZcUqja7812RVdnEKjpzOL4CvHBFG")] | |||||
// client secret | |||||
[InlineData("937it3ow87i4ery69876wqire")] | |||||
public void TestBotTokenInvalidThrowsArgumentException(string token) | |||||
{ | |||||
Assert.Throws<ArgumentException>(() => TokenUtils.ValidateToken(TokenType.Bot, token)); | |||||
} | |||||
/// <summary> | |||||
/// Tests the behavior of <see cref="TokenUtils.ValidateToken(TokenType, string)"/> | |||||
/// to see that an <see cref="ArgumentException"/> is thrown when an invalid | |||||
/// <see cref="TokenType"/> is supplied as a parameter. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// The <see cref="TokenType.User"/> type is treated as an invalid <see cref="TokenType"/>. | |||||
/// </remarks> | |||||
[Theory] | |||||
// TokenType.User | |||||
[InlineData(0)] | |||||
// out of range TokenType | |||||
[InlineData(4)] | |||||
[InlineData(7)] | |||||
public void TestUnrecognizedTokenType(int type) | |||||
{ | |||||
Assert.Throws<ArgumentException>(() => | |||||
TokenUtils.ValidateToken((TokenType)type, "MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")); | |||||
} | |||||
} | |||||
} |