diff --git a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs index ec110ab1a..fa661fb5f 100644 --- a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs @@ -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; } diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index f5b005b29..3b71c87b0 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -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> 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; } diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index 8f97c1492..d27a1ed7b 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -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; diff --git a/src/Discord.Net.Commands/Map/CommandMap.cs b/src/Discord.Net.Commands/Map/CommandMap.cs index dfc366333..141ec6fdf 100644 --- a/src/Discord.Net.Commands/Map/CommandMap.cs +++ b/src/Discord.Net.Commands/Map/CommandMap.cs @@ -6,6 +6,7 @@ namespace Discord.Commands { private readonly CommandService _service; private readonly CommandMapNode _root; + private static readonly string[] BlankAliases = { "" }; public CommandMap(CommandService service) { diff --git a/src/Discord.Net.Commands/Map/CommandMapNode.cs b/src/Discord.Net.Commands/Map/CommandMapNode.cs index 4f798e718..16f469cde 100644 --- a/src/Discord.Net.Commands/Map/CommandMapNode.cs +++ b/src/Discord.Net.Commands/Map/CommandMapNode.cs @@ -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 _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) diff --git a/src/Discord.Net.Commands/PrimitiveParsers.cs b/src/Discord.Net.Commands/PrimitiveParsers.cs index bf0622c28..e9b6aac3f 100644 --- a/src/Discord.Net.Commands/PrimitiveParsers.cs +++ b/src/Discord.Net.Commands/PrimitiveParsers.cs @@ -8,9 +8,9 @@ namespace Discord.Commands internal static class PrimitiveParsers { - private static readonly Lazy> _parsers = new Lazy>(CreateParsers); + private static readonly Lazy> Parsers = new Lazy>(CreateParsers); - public static IEnumerable SupportedTypes = _parsers.Value.Keys; + public static IEnumerable SupportedTypes = Parsers.Value.Keys; static IReadOnlyDictionary CreateParsers() { @@ -34,7 +34,7 @@ namespace Discord.Commands return parserBuilder.ToImmutable(); } - public static TryParseDelegate Get() => (TryParseDelegate)_parsers.Value[typeof(T)]; - public static Delegate Get(Type type) => _parsers.Value[type]; + public static TryParseDelegate Get() => (TryParseDelegate)Parsers.Value[typeof(T)]; + public static Delegate Get(Type type) => Parsers.Value[type]; } } diff --git a/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs b/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs index 31ab9d821..314fbb322 100644 --- a/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs @@ -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 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")); } diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index 1eeff29b5..062af0481 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -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(TypeInfo typeInfo, CommandService commands, IServiceProvider services = null) => CreateBuilder(typeInfo, commands)(services); @@ -52,8 +52,8 @@ namespace Discord.Commands } private static PropertyInfo[] GetProperties(TypeInfo ownerType) { - var result = new List(); - while (ownerType != _objectTypeInfo) + var result = new List(); + while (ownerType != ObjectTypeInfo) { foreach (var prop in ownerType.DeclaredProperties) { diff --git a/src/Discord.Net.Core/Entities/Roles/Color.cs b/src/Discord.Net.Core/Entities/Roles/Color.cs index f78eca01c..10bed840d 100644 --- a/src/Discord.Net.Core/Entities/Roles/Color.cs +++ b/src/Discord.Net.Core/Entities/Roles/Color.cs @@ -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); diff --git a/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs b/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs index c38fa8e00..6ebdbac6e 100644 --- a/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs +++ b/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs @@ -16,14 +16,14 @@ namespace Discord => await client.GetPrivateChannelAsync(id).ConfigureAwait(false) as IDMChannel; /// Gets all available DM channels for the client. public static async Task> GetDMChannelsAsync(this IDiscordClient client) - => (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).Select(x => x as IDMChannel).Where(x => x != null); + => (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).OfType(); /// Gets the group channel with the provided ID. public static async Task GetGroupChannelAsync(this IDiscordClient client, ulong id) => await client.GetPrivateChannelAsync(id).ConfigureAwait(false) as IGroupChannel; /// Gets all available group channels for the client. public static async Task> GetGroupChannelsAsync(this IDiscordClient client) - => (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).Select(x => x as IGroupChannel).Where(x => x != null); + => (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).OfType(); /// Gets the most optimal voice region for the client. public static async Task GetOptimalVoiceRegionAsync(this IDiscordClient discord) diff --git a/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs b/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs index 96059bb4f..a31721875 100644 --- a/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs +++ b/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs @@ -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; } } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Utils/Permissions.cs b/src/Discord.Net.Core/Utils/Permissions.cs index 1f25dcea9..fd0fe091a 100644 --- a/src/Discord.Net.Core/Utils/Permissions.cs +++ b/src/Discord.Net.Core/Utils/Permissions.cs @@ -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); diff --git a/src/Discord.Net.Core/Utils/TokenUtils.cs b/src/Discord.Net.Core/Utils/TokenUtils.cs new file mode 100644 index 000000000..2cc0f1041 --- /dev/null +++ b/src/Discord.Net.Core/Utils/TokenUtils.cs @@ -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 + { + /// + /// Checks the validity of the supplied token of a specific type. + /// + /// The type of token to validate. + /// The token value to validate. + /// Thrown when the supplied token string is null, empty, or contains only whitespace. + /// Thrown when the supplied TokenType or token value is invalid. + 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)); + } + } + + } +} diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index 1d45e0094..b584f5764 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -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; diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 64d3ea210..ed100287a 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -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++; diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 3c75afd6a..9b535f4cb 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -277,7 +277,7 @@ namespace Discord.Rest public async Task> 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().ToImmutableArray(); } /// @@ -306,7 +306,7 @@ namespace Discord.Rest public async Task> 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().ToImmutableArray(); } /// @@ -320,7 +320,7 @@ namespace Discord.Rest public async Task> 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().ToImmutableArray(); } /// diff --git a/src/Discord.Net.Rest/Net/DefaultRestClient.cs b/src/Discord.Net.Rest/Net/DefaultRestClient.cs index 4b4c1e045..05a568198 100644 --- a/src/Discord.Net.Rest/Net/DefaultRestClient.cs +++ b/src/Discord.Net.Rest/Net/DefaultRestClient.cs @@ -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}"); diff --git a/src/Discord.Net.Rest/Net/Queue/ClientBucket.cs b/src/Discord.Net.Rest/Net/Queue/ClientBucket.cs index f32df1bcf..cd9d8aa54 100644 --- a/src/Discord.Net.Rest/Net/Queue/ClientBucket.cs +++ b/src/Discord.Net.Rest/Net/Queue/ClientBucket.cs @@ -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 _defsByType; - private static readonly ImmutableDictionary _defsById; + private static readonly ImmutableDictionary DefsByType; + private static readonly ImmutableDictionary DefsById; static ClientBucket() { @@ -23,16 +23,16 @@ namespace Discord.Net.Queue var builder = ImmutableDictionary.CreateBuilder(); foreach (var bucket in buckets) builder.Add(bucket.Type, bucket); - _defsByType = builder.ToImmutable(); + DefsByType = builder.ToImmutable(); var builder2 = ImmutableDictionary.CreateBuilder(); 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; } diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs index 05b358b1d..1b4830da2 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs @@ -16,10 +16,10 @@ namespace Discord.Net.Queue private readonly ConcurrentDictionary _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; diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index 93a264c3c..800e04933 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -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; diff --git a/src/Discord.Net.WebSocket/ClientState.cs b/src/Discord.Net.WebSocket/ClientState.cs index 44b44e689..dad185d66 100644 --- a/src/Discord.Net.WebSocket/ClientState.cs +++ b/src/Discord.Net.WebSocket/ClientState.cs @@ -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); diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index bf71ba580..4e497346f 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -13,11 +13,11 @@ namespace Discord.WebSocket { private readonly DiscordSocketConfig _baseConfig; private readonly SemaphoreSlim _connectionGroupLock; - private int[] _shardIds; private readonly Dictionary _shardIdsToIndex; + private readonly bool _automaticShards; + private int[] _shardIds; private DiscordSocketClient[] _shards; private int _totalShards; - private readonly bool _automaticShards; /// public override int Latency { get => GetLatency(); protected set { } } @@ -26,7 +26,7 @@ namespace Discord.WebSocket /// public override IActivity Activity { get => _shards[0].Activity; protected set { } } - internal new DiscordSocketApiClient ApiClient => base.ApiClient; + internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; /// public override IReadOnlyCollection Guilds => GetGuilds().ToReadOnlyCollection(GetGuildCount); /// diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index ac36f08a3..bc579793d 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -25,9 +25,9 @@ namespace Discord.API public event Func Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); + private readonly bool _isExplicitUrl; private CancellationTokenSource _connectCancelToken; private string _gatewayUrl; - private bool _isExplicitUrl; //Store our decompression streams for zlib shared state private MemoryStream _compressed; diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 925ebf381..81538e6e7 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -83,7 +83,7 @@ namespace Discord.WebSocket /// An collection of DM channels that have been opened in this session. /// public IReadOnlyCollection DMChannels - => State.PrivateChannels.Select(x => x as SocketDMChannel).Where(x => x != null).ToImmutableArray(); + => State.PrivateChannels.OfType().ToImmutableArray(); /// /// Gets a collection of group channels opened in this session. /// @@ -98,7 +98,7 @@ namespace Discord.WebSocket /// An collection of group channels that have been opened in this session. /// public IReadOnlyCollection GroupChannels - => State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); + => State.PrivateChannels.OfType().ToImmutableArray(); /// public override IReadOnlyCollection 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); } diff --git a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs index 01eb00c83..80dec0fd4 100644 --- a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs @@ -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; diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 5b4e7d8be..fd661eb6e 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -21,10 +21,10 @@ namespace Discord.WebSocket public class SocketGroupChannel : SocketChannel, IGroupChannel, ISocketPrivateChannel, ISocketMessageChannel, ISocketAudioChannel { private readonly MessageCache _messages; + private readonly ConcurrentDictionary _voiceStates; private string _iconId; private ConcurrentDictionary _users; - private readonly ConcurrentDictionary _voiceStates; /// 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; } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs index 9bf79cdd5..07977e5e0 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs @@ -59,7 +59,7 @@ namespace Discord.WebSocket => ChannelHelper.ModifyAsync(this, Discord, func, options); /// - public async Task ConnectAsync(bool selfDeaf, bool selfMute, bool external) + public async Task ConnectAsync(bool selfDeaf = false, bool selfMute = false, bool external = false) { return await Guild.ConnectAudioAsync(Id, selfDeaf, selfMute, external).ConfigureAwait(false); } diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 400fbb75c..861c1bc38 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -171,7 +171,7 @@ namespace Discord.WebSocket /// A read-only collection of message channels found within this guild. /// public IReadOnlyCollection TextChannels - => Channels.Select(x => x as SocketTextChannel).Where(x => x != null).ToImmutableArray(); + => Channels.OfType().ToImmutableArray(); /// /// Gets a collection of all voice channels in this guild. /// @@ -179,7 +179,7 @@ namespace Discord.WebSocket /// A read-only collection of voice channels found within this guild. /// public IReadOnlyCollection VoiceChannels - => Channels.Select(x => x as SocketVoiceChannel).Where(x => x != null).ToImmutableArray(); + => Channels.OfType().ToImmutableArray(); /// /// Gets a collection of all category channels in this guild. /// @@ -187,7 +187,7 @@ namespace Discord.WebSocket /// A read-only collection of category channels found within this guild. /// public IReadOnlyCollection CategoryChannels - => Channels.Select(x => x as SocketCategoryChannel).Where(x => x != null).ToImmutableArray(); + => Channels.OfType().ToImmutableArray(); /// /// Gets the current logged-in user. /// @@ -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; diff --git a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs index 8cac95cd3..24e46df46 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs @@ -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 _); } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 7730e5363..97754da56 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -15,13 +15,13 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketUserMessage : SocketMessage, IUserMessage { + private readonly List _reactions = new List(); private bool _isMentioningEveryone, _isTTS, _isPinned; private long? _editedTimestampTicks; private ImmutableArray _attachments; private ImmutableArray _embeds; private ImmutableArray _tags; - private readonly List _reactions = new List(); - + /// public override bool IsTTS => _isTTS; /// diff --git a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs index c60368da0..dc5201ac1 100644 --- a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs +++ b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs @@ -22,8 +22,8 @@ namespace Discord.Net.WebSockets private readonly SemaphoreSlim _lock; private readonly Dictionary _headers; + private readonly IWebProxy _proxy; private ClientWebSocket _client; - private IWebProxy _proxy; private Task _task; private CancellationTokenSource _cancelTokenSource; private CancellationToken _cancelToken, _parentToken; diff --git a/test/Discord.Net.Tests/Tests.TokenUtils.cs b/test/Discord.Net.Tests/Tests.TokenUtils.cs new file mode 100644 index 000000000..dc5a93e34 --- /dev/null +++ b/test/Discord.Net.Tests/Tests.TokenUtils.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Discord +{ + public class TokenUtilsTests + { + /// + /// Tests the usage of + /// to see that when a null, empty or whitespace-only string is passed as the token, + /// it will throw an ArgumentNullException. + /// + [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(() => TokenUtils.ValidateToken(TokenType.Bearer, token)); + Assert.Throws(() => TokenUtils.ValidateToken(TokenType.Bot, token)); + Assert.Throws(() => TokenUtils.ValidateToken(TokenType.Webhook, token)); + } + + /// + /// Tests the behavior of + /// to see that valid Webhook tokens do not throw Exceptions. + /// + /// + [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. + + /// + /// Tests the behavior of + /// to see that valid Webhook tokens do not throw Exceptions. + /// + /// + [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. + + /// + /// Tests the behavior of + /// to see that valid Bot tokens do not throw Exceptions. + /// Valid Bot tokens can be strings of length 59 or above. + /// + [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); + } + + /// + /// Tests the usage of with + /// a Bot token that is invalid. + /// + [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(() => TokenUtils.ValidateToken(TokenType.Bot, token)); + } + + /// + /// Tests the behavior of + /// to see that an is thrown when an invalid + /// is supplied as a parameter. + /// + /// + /// The type is treated as an invalid . + /// + [Theory] + // TokenType.User + [InlineData(0)] + // out of range TokenType + [InlineData(4)] + [InlineData(7)] + public void TestUnrecognizedTokenType(int type) + { + Assert.Throws(() => + TokenUtils.ValidateToken((TokenType)type, "MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")); + } + } +}