| @@ -23,7 +23,7 @@ namespace Discord.Commands | |||
| public OverrideTypeReaderAttribute(Type overridenTypeReader) | |||
| { | |||
| 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; | |||
| } | |||
| @@ -10,7 +10,7 @@ namespace Discord.Commands | |||
| { | |||
| 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) | |||
| { | |||
| @@ -135,7 +135,7 @@ namespace Discord.Commands | |||
| if (builder.Name == null) | |||
| builder.Name = typeInfo.Name; | |||
| var validCommands = typeInfo.DeclaredMethods.Where(x => IsValidCommandDefinition(x)); | |||
| var validCommands = typeInfo.DeclaredMethods.Where(IsValidCommandDefinition); | |||
| foreach (var method in validCommands) | |||
| { | |||
| @@ -299,7 +299,7 @@ namespace Discord.Commands | |||
| private static bool IsValidModuleDefinition(TypeInfo typeInfo) | |||
| { | |||
| return _moduleTypeInfo.IsAssignableFrom(typeInfo) && | |||
| return ModuleTypeInfo.IsAssignableFrom(typeInfo) && | |||
| !typeInfo.IsAbstract && | |||
| !typeInfo.ContainsGenericParameters; | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| using Discord.Commands.Builders; | |||
| using Discord.Commands.Builders; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Collections.Immutable; | |||
| @@ -114,7 +114,7 @@ namespace Discord.Commands | |||
| Attributes = builder.Attributes.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; | |||
| _action = builder.Callback; | |||
| @@ -6,6 +6,7 @@ namespace Discord.Commands | |||
| { | |||
| private readonly CommandService _service; | |||
| private readonly CommandMapNode _root; | |||
| private static readonly string[] BlankAliases = { "" }; | |||
| public CommandMap(CommandService service) | |||
| { | |||
| @@ -7,7 +7,7 @@ namespace Discord.Commands | |||
| { | |||
| 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 string _name; | |||
| @@ -53,7 +53,6 @@ namespace Discord.Commands | |||
| public void RemoveCommand(CommandService service, string text, int index, CommandInfo command) | |||
| { | |||
| int nextSegment = NextSegment(text, index, service._separatorChar); | |||
| string name; | |||
| lock (_lockObj) | |||
| { | |||
| @@ -61,13 +60,13 @@ namespace Discord.Commands | |||
| _commands = _commands.Remove(command); | |||
| else | |||
| { | |||
| string name; | |||
| if (nextSegment == -1) | |||
| name = text.Substring(index); | |||
| else | |||
| 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); | |||
| if (nextNode.IsEmpty) | |||
| @@ -8,9 +8,9 @@ namespace Discord.Commands | |||
| 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() | |||
| { | |||
| @@ -34,7 +34,7 @@ namespace Discord.Commands | |||
| 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 | |||
| { | |||
| 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'", //4d3h2m | |||
| "%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) | |||
| { | |||
| 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.FromError(CommandError.ParseFailed, "Failed to parse TimeSpan")); | |||
| } | |||
| @@ -7,7 +7,7 @@ namespace Discord.Commands | |||
| { | |||
| 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) | |||
| => CreateBuilder<T>(typeInfo, commands)(services); | |||
| @@ -52,8 +52,8 @@ namespace Discord.Commands | |||
| } | |||
| 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) | |||
| { | |||
| @@ -179,6 +179,17 @@ namespace Discord | |||
| (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 | |||
| public static implicit operator StandardColor(Color color) => | |||
| StandardColor.FromArgb((int)color.RawValue); | |||
| @@ -16,14 +16,14 @@ namespace Discord | |||
| => await client.GetPrivateChannelAsync(id).ConfigureAwait(false) as IDMChannel; | |||
| /// <summary> Gets all available DM channels for the client. </summary> | |||
| 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> | |||
| public static async Task<IGroupChannel> GetGroupChannelAsync(this IDiscordClient client, ulong id) | |||
| => await client.GetPrivateChannelAsync(id).ConfigureAwait(false) as IGroupChannel; | |||
| /// <summary> Gets all available group channels for the client. </summary> | |||
| 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> | |||
| public static async Task<IVoiceRegion> GetOptimalVoiceRegionAsync(this IDiscordClient discord) | |||
| @@ -1,4 +1,4 @@ | |||
| using System; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Threading; | |||
| using System.Threading.Tasks; | |||
| @@ -60,7 +60,7 @@ namespace Discord | |||
| if (Current.Count == 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) | |||
| { | |||
| @@ -74,4 +74,4 @@ namespace Discord | |||
| public void Dispose() { Current = null; } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -119,13 +119,11 @@ namespace Discord | |||
| resolvedPermissions = mask; //Owners and administrators always have all permissions | |||
| else | |||
| { | |||
| OverwritePermissions? perms; | |||
| //Start with this user's guild permissions | |||
| resolvedPermissions = guildPermissions; | |||
| //Give/Take Everyone permissions | |||
| perms = channel.GetPermissionOverwrite(guild.EveryoneRole); | |||
| var perms = channel.GetPermissionOverwrite(guild.EveryoneRole); | |||
| if (perms != null) | |||
| resolvedPermissions = (resolvedPermissions & ~perms.Value.DenyValue) | perms.Value.AllowValue; | |||
| @@ -133,7 +131,7 @@ namespace Discord | |||
| ulong deniedPermissions = 0UL, allowedPermissions = 0UL; | |||
| foreach (var roleId in user.RoleIds) | |||
| { | |||
| IRole role = null; | |||
| IRole role; | |||
| if (roleId != guild.EveryoneRole.Id && (role = guild.GetRole(roleId)) != null) | |||
| { | |||
| 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); | |||
| } | |||
| public async Task LoginAsync(TokenType tokenType, string token) | |||
| public async Task LoginAsync(TokenType tokenType, string token, bool validateToken = true) | |||
| { | |||
| await _stateLock.WaitAsync().ConfigureAwait(false); | |||
| try | |||
| { | |||
| await LoginInternalAsync(tokenType, token).ConfigureAwait(false); | |||
| await LoginInternalAsync(tokenType, token, validateToken).ConfigureAwait(false); | |||
| } | |||
| finally { _stateLock.Release(); } | |||
| } | |||
| private async Task LoginInternalAsync(TokenType tokenType, string token) | |||
| private async Task LoginInternalAsync(TokenType tokenType, string token, bool validateToken) | |||
| { | |||
| if (_isFirstLogin) | |||
| { | |||
| @@ -79,6 +79,21 @@ namespace Discord.Rest | |||
| 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 OnLoginAsync(tokenType, token).ConfigureAwait(false); | |||
| LoginState = LoginState.LoggedIn; | |||
| @@ -1407,9 +1407,9 @@ namespace Discord.API | |||
| int argId = int.Parse(format.Substring(leftIndex + 1, rightIndex - leftIndex - 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 | |||
| rightIndex++; | |||
| @@ -277,7 +277,7 @@ namespace Discord.Rest | |||
| public async Task<IReadOnlyCollection<RestTextChannel>> GetTextChannelsAsync(RequestOptions options = null) | |||
| { | |||
| 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> | |||
| @@ -306,7 +306,7 @@ namespace Discord.Rest | |||
| public async Task<IReadOnlyCollection<RestVoiceChannel>> GetVoiceChannelsAsync(RequestOptions options = null) | |||
| { | |||
| 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> | |||
| @@ -320,7 +320,7 @@ namespace Discord.Rest | |||
| public async Task<IReadOnlyCollection<RestCategoryChannel>> GetCategoryChannelsAsync(RequestOptions options = null) | |||
| { | |||
| 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> | |||
| @@ -133,14 +133,14 @@ namespace Discord.Net.Rest | |||
| 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) | |||
| { | |||
| switch (method) | |||
| { | |||
| case "DELETE": return HttpMethod.Delete; | |||
| case "GET": return HttpMethod.Get; | |||
| case "PATCH": return _patch; | |||
| case "PATCH": return Patch; | |||
| case "POST": return HttpMethod.Post; | |||
| case "PUT": return HttpMethod.Put; | |||
| 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 | |||
| { | |||
| @@ -9,8 +9,8 @@ namespace Discord.Net.Queue | |||
| } | |||
| 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() | |||
| { | |||
| @@ -23,16 +23,16 @@ namespace Discord.Net.Queue | |||
| var builder = ImmutableDictionary.CreateBuilder<ClientBucketType, ClientBucket>(); | |||
| foreach (var bucket in buckets) | |||
| builder.Add(bucket.Type, bucket); | |||
| _defsByType = builder.ToImmutable(); | |||
| DefsByType = builder.ToImmutable(); | |||
| var builder2 = ImmutableDictionary.CreateBuilder<string, ClientBucket>(); | |||
| foreach (var bucket in buckets) | |||
| 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 string Id { get; } | |||
| @@ -16,10 +16,10 @@ namespace Discord.Net.Queue | |||
| private readonly ConcurrentDictionary<string, RequestBucket> _buckets; | |||
| private readonly SemaphoreSlim _tokenLock; | |||
| private readonly CancellationTokenSource _cancelToken; //Dispose token | |||
| private CancellationTokenSource _clearToken; | |||
| private CancellationToken _parentToken; | |||
| private CancellationToken _requestCancelToken; //Parent token + Clear token | |||
| private CancellationTokenSource _cancelToken; //Dispose token | |||
| private DateTimeOffset _waitUntil; | |||
| private Task _cleanupTask; | |||
| @@ -331,7 +331,7 @@ namespace Discord.Audio | |||
| { | |||
| if (pair.Key == value) | |||
| { | |||
| int latency = (int)(Environment.TickCount - pair.Value); | |||
| int latency = Environment.TickCount - pair.Value; | |||
| int before = UdpLatency; | |||
| UdpLatency = latency; | |||
| @@ -72,7 +72,7 @@ namespace Discord.WebSocket | |||
| switch (channel) | |||
| { | |||
| case SocketDMChannel dmChannel: | |||
| _dmChannels.TryRemove(dmChannel.Recipient.Id, out var _); | |||
| _dmChannels.TryRemove(dmChannel.Recipient.Id, out _); | |||
| break; | |||
| case SocketGroupChannel _: | |||
| _groupChannels.TryRemove(id); | |||
| @@ -13,11 +13,11 @@ namespace Discord.WebSocket | |||
| { | |||
| private readonly DiscordSocketConfig _baseConfig; | |||
| private readonly SemaphoreSlim _connectionGroupLock; | |||
| private int[] _shardIds; | |||
| private readonly Dictionary<int, int> _shardIdsToIndex; | |||
| private readonly bool _automaticShards; | |||
| private int[] _shardIds; | |||
| private DiscordSocketClient[] _shards; | |||
| private int _totalShards; | |||
| private readonly bool _automaticShards; | |||
| /// <inheritdoc /> | |||
| public override int Latency { get => GetLatency(); protected set { } } | |||
| @@ -26,7 +26,7 @@ namespace Discord.WebSocket | |||
| /// <inheritdoc /> | |||
| 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 /> | |||
| public override IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(GetGuildCount); | |||
| /// <inheritdoc /> | |||
| @@ -25,9 +25,9 @@ namespace Discord.API | |||
| 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 bool _isExplicitUrl; | |||
| private CancellationTokenSource _connectCancelToken; | |||
| private string _gatewayUrl; | |||
| private bool _isExplicitUrl; | |||
| //Store our decompression streams for zlib shared state | |||
| private MemoryStream _compressed; | |||
| @@ -83,7 +83,7 @@ namespace Discord.WebSocket | |||
| /// An collection of DM channels that have been opened in this session. | |||
| /// </returns> | |||
| public IReadOnlyCollection<SocketDMChannel> DMChannels | |||
| => State.PrivateChannels.Select(x => x as SocketDMChannel).Where(x => x != null).ToImmutableArray(); | |||
| => State.PrivateChannels.OfType<SocketDMChannel>().ToImmutableArray(); | |||
| /// <summary> | |||
| /// Gets a collection of group channels opened in this session. | |||
| /// </summary> | |||
| @@ -98,7 +98,7 @@ namespace Discord.WebSocket | |||
| /// An collection of group channels that have been opened in this session. | |||
| /// </returns> | |||
| public IReadOnlyCollection<SocketGroupChannel> GroupChannels | |||
| => State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); | |||
| => State.PrivateChannels.OfType<SocketGroupChannel>().ToImmutableArray(); | |||
| /// <inheritdoc /> | |||
| public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection(); | |||
| @@ -1747,7 +1747,7 @@ namespace Discord.WebSocket | |||
| if (eventHandler.HasSubscribers) | |||
| { | |||
| if (HandlerTimeout.HasValue) | |||
| await TimeoutWrap(name, () => eventHandler.InvokeAsync()).ConfigureAwait(false); | |||
| await TimeoutWrap(name, eventHandler.InvokeAsync).ConfigureAwait(false); | |||
| else | |||
| await eventHandler.InvokeAsync().ConfigureAwait(false); | |||
| } | |||
| @@ -39,8 +39,8 @@ namespace Discord.Audio | |||
| private readonly JsonSerializer _serializer; | |||
| private readonly SemaphoreSlim _connectionLock; | |||
| private readonly IUdpSocket _udp; | |||
| private CancellationTokenSource _connectCancelToken; | |||
| private IUdpSocket _udp; | |||
| private bool _isDisposed; | |||
| private ulong _nextKeepalive; | |||
| @@ -21,10 +21,10 @@ namespace Discord.WebSocket | |||
| public class SocketGroupChannel : SocketChannel, IGroupChannel, ISocketPrivateChannel, ISocketMessageChannel, ISocketAudioChannel | |||
| { | |||
| private readonly MessageCache _messages; | |||
| private readonly ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates; | |||
| private string _iconId; | |||
| private ConcurrentDictionary<ulong, SocketGroupUser> _users; | |||
| private readonly ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates; | |||
| /// <inheritdoc /> | |||
| public string Name { get; private set; } | |||
| @@ -153,7 +153,7 @@ namespace Discord.WebSocket | |||
| internal SocketGroupUser GetOrAddUser(UserModel model) | |||
| { | |||
| if (_users.TryGetValue(model.Id, out SocketGroupUser user)) | |||
| return user as SocketGroupUser; | |||
| return user; | |||
| else | |||
| { | |||
| var privateUser = SocketGroupUser.Create(this, Discord.State, model); | |||
| @@ -167,7 +167,7 @@ namespace Discord.WebSocket | |||
| if (_users.TryRemove(id, out SocketGroupUser user)) | |||
| { | |||
| user.GlobalUser.RemoveRef(Discord); | |||
| return user as SocketGroupUser; | |||
| return user; | |||
| } | |||
| return null; | |||
| } | |||
| @@ -59,7 +59,7 @@ namespace Discord.WebSocket | |||
| => ChannelHelper.ModifyAsync(this, Discord, func, options); | |||
| /// <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); | |||
| } | |||
| @@ -171,7 +171,7 @@ namespace Discord.WebSocket | |||
| /// A read-only collection of message channels found within this guild. | |||
| /// </returns> | |||
| public IReadOnlyCollection<SocketTextChannel> TextChannels | |||
| => Channels.Select(x => x as SocketTextChannel).Where(x => x != null).ToImmutableArray(); | |||
| => Channels.OfType<SocketTextChannel>().ToImmutableArray(); | |||
| /// <summary> | |||
| /// Gets a collection of all voice channels in this guild. | |||
| /// </summary> | |||
| @@ -179,7 +179,7 @@ namespace Discord.WebSocket | |||
| /// A read-only collection of voice channels found within this guild. | |||
| /// </returns> | |||
| public IReadOnlyCollection<SocketVoiceChannel> VoiceChannels | |||
| => Channels.Select(x => x as SocketVoiceChannel).Where(x => x != null).ToImmutableArray(); | |||
| => Channels.OfType<SocketVoiceChannel>().ToImmutableArray(); | |||
| /// <summary> | |||
| /// Gets a collection of all category channels in this guild. | |||
| /// </summary> | |||
| @@ -187,7 +187,7 @@ namespace Discord.WebSocket | |||
| /// A read-only collection of category channels found within this guild. | |||
| /// </returns> | |||
| public IReadOnlyCollection<SocketCategoryChannel> CategoryChannels | |||
| => Channels.Select(x => x as SocketCategoryChannel).Where(x => x != null).ToImmutableArray(); | |||
| => Channels.OfType<SocketCategoryChannel>().ToImmutableArray(); | |||
| /// <summary> | |||
| /// Gets the current logged-in user. | |||
| /// </summary> | |||
| @@ -870,7 +870,7 @@ namespace Discord.WebSocket | |||
| await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); | |||
| } | |||
| catch (Exception) | |||
| catch | |||
| { | |||
| await DisconnectAudioInternalAsync().ConfigureAwait(false); | |||
| throw; | |||
| @@ -887,7 +887,7 @@ namespace Discord.WebSocket | |||
| throw new TimeoutException(); | |||
| return await promise.Task.ConfigureAwait(false); | |||
| } | |||
| catch (Exception) | |||
| catch | |||
| { | |||
| await DisconnectAudioAsync().ConfigureAwait(false); | |||
| throw; | |||
| @@ -28,7 +28,7 @@ namespace Discord.WebSocket | |||
| _orderedMessages.Enqueue(message.Id); | |||
| 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}")] | |||
| public class SocketUserMessage : SocketMessage, IUserMessage | |||
| { | |||
| private readonly List<SocketReaction> _reactions = new List<SocketReaction>(); | |||
| private bool _isMentioningEveryone, _isTTS, _isPinned; | |||
| private long? _editedTimestampTicks; | |||
| private ImmutableArray<Attachment> _attachments; | |||
| private ImmutableArray<Embed> _embeds; | |||
| private ImmutableArray<ITag> _tags; | |||
| private readonly List<SocketReaction> _reactions = new List<SocketReaction>(); | |||
| /// <inheritdoc /> | |||
| public override bool IsTTS => _isTTS; | |||
| /// <inheritdoc /> | |||
| @@ -22,8 +22,8 @@ namespace Discord.Net.WebSockets | |||
| private readonly SemaphoreSlim _lock; | |||
| private readonly Dictionary<string, string> _headers; | |||
| private readonly IWebProxy _proxy; | |||
| private ClientWebSocket _client; | |||
| private IWebProxy _proxy; | |||
| private Task _task; | |||
| private CancellationTokenSource _cancelTokenSource; | |||
| 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")); | |||
| } | |||
| } | |||
| } | |||