@@ -16,13 +16,9 @@ Our stable builds available from NuGet through the Discord.Net metapackage: | |||
The individual components may also be installed from NuGet: | |||
- [Discord.Net.Commands](https://www.nuget.org/packages/Discord.Net.Commands/) | |||
- [Discord.Net.Rest](https://www.nuget.org/packages/Discord.Net.Rest/) | |||
- [Discord.Net.Rpc](https://www.nuget.org/packages/Discord.Net.Rpc/) | |||
- [Discord.Net.WebSocket](https://www.nuget.org/packages/Discord.Net.WebSocket/) | |||
- [Discord.Net.Webhook](https://www.nuget.org/packages/Discord.Net.Webhook/) | |||
The following provider is available for platforms not supporting .NET Standard 1.3: | |||
- [Discord.Net.Providers.WS4Net](https://www.nuget.org/packages/Discord.Net.Providers.WS4Net/) | |||
### Unstable (MyGet) | |||
Nightly builds are available through our MyGet feed (`https://www.myget.org/F/discord-net/api/v3/index.json`). | |||
@@ -41,5 +37,4 @@ The .NET Core workload must be selected during Visual Studio installation. | |||
## Known Issues | |||
### WebSockets (Win7 and earlier) | |||
.NET Core 1.1 does not support WebSockets on Win7 and earlier. It's recommended to use the Discord.Net.Providers.WS4Net package until this is resolved. | |||
Track the issue [here](https://github.com/dotnet/corefx/issues/9503). | |||
.NET Core 1.1 does not support WebSockets on Win7 and earlier. This issue has been fixed since the release of .NET Core 2.1. It is recommended to target .NET Core 2.1 or above for your project if you wish to run your bot on legacy platforms; alternatively, you may choose to install the [Discord.Net.Providers.WS4Net](https://www.nuget.org/packages/Discord.Net.Providers.WS4Net/) package. |
@@ -7,7 +7,7 @@ namespace Discord.Commands | |||
{ | |||
public string Text { get; } | |||
public RunMode RunMode { get; set; } = RunMode.Default; | |||
public bool? IgnoreExtraArgs { get; set; } | |||
public bool? IgnoreExtraArgs { get; } | |||
public CommandAttribute() | |||
{ | |||
@@ -17,5 +17,10 @@ namespace Discord.Commands | |||
{ | |||
Text = text; | |||
} | |||
public CommandAttribute(string text, bool ignoreExtraArgs) | |||
{ | |||
Text = text; | |||
IgnoreExtraArgs = ignoreExtraArgs; | |||
} | |||
} | |||
} |
@@ -7,13 +7,13 @@ namespace Discord.Commands | |||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] | |||
public class OverrideTypeReaderAttribute : Attribute | |||
{ | |||
private static readonly TypeInfo _typeReaderTypeInfo = typeof(TypeReader).GetTypeInfo(); | |||
private static readonly TypeInfo TypeReaderTypeInfo = typeof(TypeReader).GetTypeInfo(); | |||
public Type TypeReader { get; } | |||
public OverrideTypeReaderAttribute(Type overridenTypeReader) | |||
{ | |||
if (!_typeReaderTypeInfo.IsAssignableFrom(overridenTypeReader.GetTypeInfo())) | |||
if (!TypeReaderTypeInfo.IsAssignableFrom(overridenTypeReader.GetTypeInfo())) | |||
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; | |||
} | |||
@@ -118,7 +118,7 @@ namespace Discord.Commands | |||
var typeInfo = type.GetTypeInfo(); | |||
if (_typedModuleDefs.ContainsKey(type)) | |||
throw new ArgumentException($"This module has already been added."); | |||
throw new ArgumentException("This module has already been added."); | |||
var module = (await ModuleClassBuilder.BuildAsync(this, services, typeInfo).ConfigureAwait(false)).FirstOrDefault(); | |||
@@ -241,7 +241,7 @@ namespace Discord.Commands | |||
{ | |||
if (_defaultTypeReaders.ContainsKey(type)) | |||
_ = _cmdLogger.WarningAsync($"The default TypeReader for {type.FullName} was replaced by {reader.GetType().FullName}." + | |||
$"To suppress this message, use AddTypeReader<T>(reader, true)."); | |||
"To suppress this message, use AddTypeReader<T>(reader, true)."); | |||
AddTypeReader(type, reader, true); | |||
} | |||
/// <summary> | |||
@@ -1,4 +1,4 @@ | |||
using Discord.Commands.Builders; | |||
using Discord.Commands.Builders; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
@@ -63,7 +63,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; | |||
@@ -1,4 +1,4 @@ | |||
using System.Collections.Generic; | |||
using System.Collections.Generic; | |||
namespace Discord.Commands | |||
{ | |||
@@ -6,7 +6,7 @@ namespace Discord.Commands | |||
{ | |||
private readonly CommandService _service; | |||
private readonly CommandMapNode _root; | |||
private static readonly string[] _blankAliases = new[] { "" }; | |||
private static readonly string[] BlankAliases = { "" }; | |||
public CommandMap(CommandService service) | |||
{ | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
@@ -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; | |||
@@ -52,7 +52,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) | |||
{ | |||
@@ -60,13 +59,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) | |||
@@ -100,7 +99,7 @@ namespace Discord.Commands | |||
} | |||
//Check if this is the last command segment before args | |||
nextSegment = NextSegment(text, index, _whitespaceChars, service._separatorChar); | |||
nextSegment = NextSegment(text, index, WhitespaceChars, service._separatorChar); | |||
if (nextSegment != -1) | |||
{ | |||
name = text.Substring(index, nextSegment - index); | |||
@@ -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")); | |||
} | |||
@@ -71,8 +71,8 @@ namespace Discord.Commands | |||
.Where(x => string.Equals(input, (x as IGuildUser)?.Nickname, StringComparison.OrdinalIgnoreCase)) | |||
.ForEachAsync(channelUser => AddResult(results, channelUser as T, (channelUser as IGuildUser).Nickname == input ? 0.65f : 0.55f)); | |||
foreach (var guildUser in guildUsers.Where(x => string.Equals(input, (x as IGuildUser).Nickname, StringComparison.OrdinalIgnoreCase))) | |||
AddResult(results, guildUser as T, (guildUser as IGuildUser).Nickname == input ? 0.60f : 0.50f); | |||
foreach (var guildUser in guildUsers.Where(x => string.Equals(input, x.Nickname, StringComparison.OrdinalIgnoreCase))) | |||
AddResult(results, guildUser as T, guildUser.Nickname == input ? 0.60f : 0.50f); | |||
} | |||
if (results.Count > 0) | |||
@@ -1,4 +1,5 @@ | |||
using System.Collections.Generic; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
namespace Discord.Commands | |||
@@ -53,6 +54,8 @@ namespace Discord.Commands | |||
public static ParseResult FromError(CommandError error, string reason) | |||
=> new ParseResult(null, null, error, reason); | |||
public static ParseResult FromError(Exception ex) | |||
=> FromError(CommandError.Exception, ex.Message); | |||
public static ParseResult FromError(IResult result) | |||
=> new ParseResult(null, null, result.Error, result.ErrorReason); | |||
@@ -1,4 +1,5 @@ | |||
using System.Collections.Generic; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
namespace Discord.Commands | |||
@@ -18,6 +19,8 @@ namespace Discord.Commands | |||
=> new PreconditionGroupResult(null, null, null); | |||
public static PreconditionGroupResult FromError(string reason, ICollection<PreconditionResult> preconditions) | |||
=> new PreconditionGroupResult(CommandError.UnmetPrecondition, reason, preconditions); | |||
public static new PreconditionGroupResult FromError(Exception ex) | |||
=> new PreconditionGroupResult(CommandError.Exception, ex.Message, null); | |||
public static new PreconditionGroupResult FromError(IResult result) //needed? | |||
=> new PreconditionGroupResult(result.Error, result.ErrorReason, null); | |||
@@ -1,4 +1,5 @@ | |||
using System.Diagnostics; | |||
using System; | |||
using System.Diagnostics; | |||
namespace Discord.Commands | |||
{ | |||
@@ -20,6 +21,8 @@ namespace Discord.Commands | |||
=> new PreconditionResult(null, null); | |||
public static PreconditionResult FromError(string reason) | |||
=> new PreconditionResult(CommandError.UnmetPrecondition, reason); | |||
public static PreconditionResult FromError(Exception ex) | |||
=> new PreconditionResult(CommandError.Exception, ex.Message); | |||
public static PreconditionResult FromError(IResult result) | |||
=> new PreconditionResult(result.Error, result.ErrorReason); | |||
@@ -1,4 +1,5 @@ | |||
using System.Collections.Generic; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
namespace Discord.Commands | |||
@@ -26,6 +27,8 @@ namespace Discord.Commands | |||
=> new SearchResult(text, commands, null, null); | |||
public static SearchResult FromError(CommandError error, string reason) | |||
=> new SearchResult(null, null, error, reason); | |||
public static SearchResult FromError(Exception ex) | |||
=> FromError(CommandError.Exception, ex.Message); | |||
public static SearchResult FromError(IResult result) | |||
=> new SearchResult(null, null, result.Error, result.ErrorReason); | |||
@@ -50,6 +50,8 @@ namespace Discord.Commands | |||
=> new TypeReaderResult(values, null, null); | |||
public static TypeReaderResult FromError(CommandError error, string reason) | |||
=> new TypeReaderResult(null, error, reason); | |||
public static TypeReaderResult FromError(Exception ex) | |||
=> FromError(CommandError.Exception, ex.Message); | |||
public static TypeReaderResult FromError(IResult result) | |||
=> new TypeReaderResult(null, result.Error, result.ErrorReason); | |||
@@ -8,7 +8,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); | |||
@@ -54,7 +54,7 @@ namespace Discord.Commands | |||
private static System.Reflection.PropertyInfo[] GetProperties(TypeInfo ownerType) | |||
{ | |||
var result = new List<System.Reflection.PropertyInfo>(); | |||
while (ownerType != _objectTypeInfo) | |||
while (ownerType != ObjectTypeInfo) | |||
{ | |||
foreach (var prop in ownerType.DeclaredProperties) | |||
{ | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text; | |||
@@ -9,7 +9,7 @@ namespace Discord | |||
/// <summary> | |||
/// Represents an entry in an audit log | |||
/// </summary> | |||
public interface IAuditLogEntry : IEntity<ulong> | |||
public interface IAuditLogEntry : ISnowflakeEntity | |||
{ | |||
/// <summary> | |||
/// The action which occured to create this entry | |||
@@ -239,7 +239,7 @@ namespace Discord | |||
get => _name; | |||
set | |||
{ | |||
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException($"Field name must not be null, empty or entirely whitespace.", nameof(Name)); | |||
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("Field name must not be null, empty or entirely whitespace.", nameof(Name)); | |||
if (value.Length > MaxFieldNameLength) throw new ArgumentException($"Field name length must be less than or equal to {MaxFieldNameLength}.", nameof(Name)); | |||
_name = value; | |||
} | |||
@@ -251,7 +251,7 @@ namespace Discord | |||
set | |||
{ | |||
var stringValue = value?.ToString(); | |||
if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException($"Field value must not be null or empty.", nameof(Value)); | |||
if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException("Field value must not be null or empty.", nameof(Value)); | |||
if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException($"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value)); | |||
_value = stringValue; | |||
} | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Diagnostics; | |||
namespace Discord | |||
@@ -20,6 +20,6 @@ namespace Discord | |||
} | |||
private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; | |||
public override string ToString() => Url.ToString(); | |||
public override string ToString() => Url; | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Diagnostics; | |||
namespace Discord | |||
@@ -20,6 +20,6 @@ namespace Discord | |||
} | |||
private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; | |||
public override string ToString() => Url.ToString(); | |||
public override string ToString() => Url; | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Diagnostics; | |||
namespace Discord | |||
@@ -18,6 +18,6 @@ namespace Discord | |||
} | |||
private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; | |||
public override string ToString() => Url.ToString(); | |||
public override string ToString() => Url; | |||
} | |||
} |
@@ -100,6 +100,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); | |||
@@ -12,12 +12,12 @@ namespace Discord | |||
public static async Task<IDMChannel> GetDMChannelAsync(this IDiscordClient client, ulong id) | |||
=> await client.GetPrivateChannelAsync(id).ConfigureAwait(false) as IDMChannel; | |||
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>(); | |||
public static async Task<IGroupChannel> GetGroupChannelAsync(this IDiscordClient client, ulong id) | |||
=> await client.GetPrivateChannelAsync(id).ConfigureAwait(false) as IGroupChannel; | |||
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>(); | |||
public static async Task<IVoiceRegion> GetOptimalVoiceRegionAsync(this IDiscordClient discord) | |||
{ | |||
@@ -1,9 +1,9 @@ | |||
namespace Discord | |||
namespace Discord | |||
{ | |||
public static class Format | |||
{ | |||
// Characters which need escaping | |||
private static string[] SensitiveCharacters = { "\\", "*", "_", "~", "`" }; | |||
private static readonly string[] SensitiveCharacters = { "\\", "*", "_", "~", "`" }; | |||
/// <summary> Returns a markdown-formatted string with bold formatting. </summary> | |||
public static string Bold(string text) => $"**{text}**"; | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Globalization; | |||
using System.Text; | |||
@@ -139,22 +139,22 @@ namespace Discord | |||
if (user != null) | |||
return $"@{guildUser?.Nickname ?? user?.Username}"; | |||
else | |||
return $""; | |||
return ""; | |||
case TagHandling.NameNoPrefix: | |||
if (user != null) | |||
return $"{guildUser?.Nickname ?? user?.Username}"; | |||
else | |||
return $""; | |||
return ""; | |||
case TagHandling.FullName: | |||
if (user != null) | |||
return $"@{user.Username}#{user.Discriminator}"; | |||
else | |||
return $""; | |||
return ""; | |||
case TagHandling.FullNameNoPrefix: | |||
if (user != null) | |||
return $"{user.Username}#{user.Discriminator}"; | |||
else | |||
return $""; | |||
return ""; | |||
case TagHandling.Sanitize: | |||
if (guildUser != null && guildUser.Nickname == null) | |||
return MentionUser($"{SanitizeChar}{tag.Key}", false); | |||
@@ -176,13 +176,13 @@ namespace Discord | |||
if (channel != null) | |||
return $"#{channel.Name}"; | |||
else | |||
return $""; | |||
return ""; | |||
case TagHandling.NameNoPrefix: | |||
case TagHandling.FullNameNoPrefix: | |||
if (channel != null) | |||
return $"{channel.Name}"; | |||
else | |||
return $""; | |||
return ""; | |||
case TagHandling.Sanitize: | |||
return MentionChannel($"{SanitizeChar}{tag.Key}"); | |||
} | |||
@@ -201,13 +201,13 @@ namespace Discord | |||
if (role != null) | |||
return $"@{role.Name}"; | |||
else | |||
return $""; | |||
return ""; | |||
case TagHandling.NameNoPrefix: | |||
case TagHandling.FullNameNoPrefix: | |||
if (role != null) | |||
return $"{role.Name}"; | |||
else | |||
return $""; | |||
return ""; | |||
case TagHandling.Sanitize: | |||
return MentionRole($"{SanitizeChar}{tag.Key}"); | |||
} | |||
@@ -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; } | |||
} | |||
} | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
using System.Runtime.CompilerServices; | |||
using System.Runtime.CompilerServices; | |||
namespace Discord | |||
{ | |||
@@ -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); | |||
@@ -151,7 +149,7 @@ namespace Discord | |||
if (perms != null) | |||
resolvedPermissions = (resolvedPermissions & ~perms.Value.DenyValue) | perms.Value.AllowValue; | |||
if (channel is ITextChannel textChannel) | |||
if (channel is ITextChannel) | |||
{ | |||
if (!GetValue(resolvedPermissions, ChannelPermission.ViewChannel)) | |||
{ | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
namespace Discord | |||
{ | |||
@@ -198,7 +198,7 @@ namespace Discord | |||
for (var i = 0; i < roles.Length; i++) | |||
{ | |||
if (roles[i] == guildId) | |||
throw new ArgumentException($"The everyone role cannot be assigned to a user", name); | |||
throw new ArgumentException("The everyone role cannot be assigned to a user", name); | |||
} | |||
} | |||
} | |||
@@ -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)); | |||
} | |||
} | |||
} | |||
} |
@@ -20,7 +20,7 @@ namespace Discord.API | |||
[JsonProperty("role_name")] | |||
public string OverwriteRoleName { get; set; } | |||
[JsonProperty("type")] | |||
public string OverwriteType { get; set; } | |||
public PermissionTarget OverwriteType { get; set; } | |||
[JsonProperty("id")] | |||
public ulong? OverwriteTargetId { get; set; } | |||
} | |||
@@ -55,11 +55,11 @@ namespace Discord.Rest | |||
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) | |||
{ | |||
@@ -73,11 +73,26 @@ 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; | |||
} | |||
catch (Exception) | |||
catch | |||
{ | |||
await LogoutInternalAsync().ConfigureAwait(false); | |||
throw; | |||
@@ -47,7 +47,7 @@ namespace Discord.Rest | |||
public static async Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(BaseDiscordClient client, RequestOptions options) | |||
{ | |||
var models = await client.ApiClient.GetMyConnectionsAsync(options).ConfigureAwait(false); | |||
return models.Select(x => RestConnection.Create(x)).ToImmutableArray(); | |||
return models.Select(RestConnection.Create).ToImmutableArray(); | |||
} | |||
public static async Task<RestInviteMetadata> GetInviteAsync(BaseDiscordClient client, | |||
@@ -43,9 +43,11 @@ namespace Discord.API | |||
public TokenType AuthTokenType { get; private set; } | |||
internal string AuthToken { get; private set; } | |||
internal IRestClient RestClient { get; private set; } | |||
internal ulong? CurrentUserId { get; set;} | |||
internal ulong? CurrentUserId { get; set; } | |||
public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, | |||
internal JsonSerializer Serializer => _serializer; | |||
public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, | |||
JsonSerializer serializer = null) | |||
{ | |||
_restClientProvider = restClientProvider; | |||
@@ -123,7 +125,7 @@ namespace Discord.API | |||
LoginState = LoginState.LoggedIn; | |||
} | |||
catch (Exception) | |||
catch | |||
{ | |||
await LogoutInternalAsync().ConfigureAwait(false); | |||
throw; | |||
@@ -235,7 +237,7 @@ namespace Discord.API | |||
internal Task<TResponse> SendMultipartAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids, | |||
ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) | |||
=> SendMultipartAsync<TResponse>(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); | |||
public async Task<TResponse> SendMultipartAsync<TResponse>(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs, | |||
public async Task<TResponse> SendMultipartAsync<TResponse>(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs, | |||
string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) | |||
{ | |||
options = options ?? new RequestOptions(); | |||
@@ -414,7 +416,7 @@ namespace Discord.API | |||
var ids = new BucketIds(guildId: guildId); | |||
await SendAsync("DELETE", () => $"guilds/{guildId}/members/{userId}/roles/{roleId}", ids, options: options); | |||
} | |||
//Channel Messages | |||
public async Task<Message> GetChannelMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) | |||
{ | |||
@@ -490,7 +492,7 @@ namespace Discord.API | |||
if (args.Content?.Length > DiscordConfig.MaxMessageSize) | |||
throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); | |||
options = RequestOptions.CreateOrClone(options); | |||
return await SendJsonAsync<Message>("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args, new BucketIds(), clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); | |||
} | |||
public async Task<Message> UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) | |||
@@ -737,7 +739,7 @@ namespace Discord.API | |||
Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); | |||
Preconditions.NotNullOrWhitespace(args.RegionId, nameof(args.RegionId)); | |||
options = RequestOptions.CreateOrClone(options); | |||
return await SendJsonAsync<Guild>("POST", () => "guilds", args, new BucketIds(), options: options).ConfigureAwait(false); | |||
} | |||
public async Task<Guild> DeleteGuildAsync(ulong guildId, RequestOptions options = null) | |||
@@ -964,7 +966,7 @@ namespace Discord.API | |||
{ | |||
Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); | |||
options = RequestOptions.CreateOrClone(options); | |||
return await SendAsync<Invite>("DELETE", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); | |||
} | |||
@@ -1163,7 +1165,7 @@ namespace Discord.API | |||
int limit = args.Limit.GetValueOrDefault(int.MaxValue); | |||
ulong afterGuildId = args.AfterGuildId.GetValueOrDefault(0); | |||
return await SendAsync<IReadOnlyCollection<UserGuild>>("GET", () => $"users/@me/guilds?limit={limit}&after={afterGuildId}", new BucketIds(), options: options).ConfigureAwait(false); | |||
} | |||
public async Task<Application> GetMyApplicationAsync(RequestOptions options = null) | |||
@@ -1263,7 +1265,7 @@ namespace Discord.API | |||
Preconditions.NotNull(args, nameof(args)); | |||
Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); | |||
options = RequestOptions.CreateOrClone(options); | |||
if (AuthTokenType == TokenType.Webhook) | |||
return await SendJsonAsync<Webhook>("PATCH", () => $"webhooks/{webhookId}/{AuthToken}", args, new BucketIds(), options: options).ConfigureAwait(false); | |||
else | |||
@@ -1392,9 +1394,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++; | |||
@@ -26,18 +26,16 @@ namespace Discord.Rest | |||
var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); | |||
var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); | |||
var type = typeModel.NewValue.ToObject<ChannelType>(); | |||
var name = nameModel.NewValue.ToObject<string>(); | |||
var type = typeModel.NewValue.ToObject<ChannelType>(discord.ApiClient.Serializer); | |||
var name = nameModel.NewValue.ToObject<string>(discord.ApiClient.Serializer); | |||
foreach (var overwrite in overwritesModel.NewValue) | |||
{ | |||
var deny = overwrite.Value<ulong>("deny"); | |||
var _type = overwrite.Value<string>("type"); | |||
var permType = overwrite.Value<PermissionTarget>("type"); | |||
var id = overwrite.Value<ulong>("id"); | |||
var allow = overwrite.Value<ulong>("allow"); | |||
PermissionTarget permType = _type == "member" ? PermissionTarget.User : PermissionTarget.Role; | |||
overwrites.Add(new Overwrite(id, permType, new OverwritePermissions(allow, deny))); | |||
} | |||
@@ -27,11 +27,11 @@ namespace Discord.Rest | |||
var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); | |||
var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); | |||
var overwrites = overwritesModel.OldValue.ToObject<API.Overwrite[]>() | |||
var overwrites = overwritesModel.OldValue.ToObject<API.Overwrite[]>(discord.ApiClient.Serializer) | |||
.Select(x => new Overwrite(x.TargetId, x.TargetType, new OverwritePermissions(x.Allow, x.Deny))) | |||
.ToList(); | |||
var type = typeModel.OldValue.ToObject<ChannelType>(); | |||
var name = nameModel.OldValue.ToObject<string>(); | |||
var type = typeModel.OldValue.ToObject<ChannelType>(discord.ApiClient.Serializer); | |||
var name = nameModel.OldValue.ToObject<string>(discord.ApiClient.Serializer); | |||
var id = entry.TargetId.Value; | |||
return new ChannelDeleteAuditLogData(id, name, type, overwrites.ToReadOnlyCollection()); | |||
@@ -23,14 +23,14 @@ namespace Discord.Rest | |||
var bitrateModel = changes.FirstOrDefault(x => x.ChangedProperty == "bitrate"); | |||
var userLimitModel = changes.FirstOrDefault(x => x.ChangedProperty == "user_limit"); | |||
string oldName = nameModel?.OldValue?.ToObject<string>(), | |||
newName = nameModel?.NewValue?.ToObject<string>(); | |||
string oldTopic = topicModel?.OldValue?.ToObject<string>(), | |||
newTopic = topicModel?.NewValue?.ToObject<string>(); | |||
int? oldBitrate = bitrateModel?.OldValue?.ToObject<int>(), | |||
newBitrate = bitrateModel?.NewValue?.ToObject<int>(); | |||
int? oldLimit = userLimitModel?.OldValue?.ToObject<int>(), | |||
newLimit = userLimitModel?.NewValue?.ToObject<int>(); | |||
string oldName = nameModel?.OldValue?.ToObject<string>(discord.ApiClient.Serializer), | |||
newName = nameModel?.NewValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
string oldTopic = topicModel?.OldValue?.ToObject<string>(discord.ApiClient.Serializer), | |||
newTopic = topicModel?.NewValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
int? oldBitrate = bitrateModel?.OldValue?.ToObject<int>(discord.ApiClient.Serializer), | |||
newBitrate = bitrateModel?.NewValue?.ToObject<int>(discord.ApiClient.Serializer); | |||
int? oldLimit = userLimitModel?.OldValue?.ToObject<int>(discord.ApiClient.Serializer), | |||
newLimit = userLimitModel?.NewValue?.ToObject<int>(discord.ApiClient.Serializer); | |||
var before = new ChannelInfo(oldName, oldTopic, oldBitrate, oldLimit); | |||
var after = new ChannelInfo(newName, newTopic, newBitrate, newLimit); | |||
@@ -21,7 +21,7 @@ namespace Discord.Rest | |||
{ | |||
var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); | |||
var emoteName = change.NewValue?.ToObject<string>(); | |||
var emoteName = change.NewValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
return new EmoteCreateAuditLogData(entry.TargetId.Value, emoteName); | |||
} | |||
@@ -17,7 +17,7 @@ namespace Discord.Rest | |||
{ | |||
var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); | |||
var emoteName = change.OldValue?.ToObject<string>(); | |||
var emoteName = change.OldValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
return new EmoteDeleteAuditLogData(entry.TargetId.Value, emoteName); | |||
} | |||
@@ -18,8 +18,8 @@ namespace Discord.Rest | |||
{ | |||
var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); | |||
var newName = change.NewValue?.ToObject<string>(); | |||
var oldName = change.OldValue?.ToObject<string>(); | |||
var newName = change.NewValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
var oldName = change.OldValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
return new EmoteUpdateAuditLogData(entry.TargetId.Value, oldName, newName); | |||
} | |||
@@ -28,26 +28,26 @@ namespace Discord.Rest | |||
var mfaLevelModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); | |||
var contentFilterModel = changes.FirstOrDefault(x => x.ChangedProperty == "afk_timeout"); | |||
int? oldAfkTimeout = afkTimeoutModel?.OldValue?.ToObject<int>(), | |||
newAfkTimeout = afkTimeoutModel?.NewValue?.ToObject<int>(); | |||
DefaultMessageNotifications? oldDefaultMessageNotifications = defaultMessageNotificationsModel?.OldValue?.ToObject<DefaultMessageNotifications>(), | |||
newDefaultMessageNotifications = defaultMessageNotificationsModel?.NewValue?.ToObject<DefaultMessageNotifications>(); | |||
ulong? oldAfkChannelId = afkChannelModel?.OldValue?.ToObject<ulong>(), | |||
newAfkChannelId = afkChannelModel?.NewValue?.ToObject<ulong>(); | |||
string oldName = nameModel?.OldValue?.ToObject<string>(), | |||
newName = nameModel?.NewValue?.ToObject<string>(); | |||
string oldRegionId = regionIdModel?.OldValue?.ToObject<string>(), | |||
newRegionId = regionIdModel?.NewValue?.ToObject<string>(); | |||
string oldIconHash = iconHashModel?.OldValue?.ToObject<string>(), | |||
newIconHash = iconHashModel?.NewValue?.ToObject<string>(); | |||
VerificationLevel? oldVerificationLevel = verificationLevelModel?.OldValue?.ToObject<VerificationLevel>(), | |||
newVerificationLevel = verificationLevelModel?.NewValue?.ToObject<VerificationLevel>(); | |||
ulong? oldOwnerId = ownerIdModel?.OldValue?.ToObject<ulong>(), | |||
newOwnerId = ownerIdModel?.NewValue?.ToObject<ulong>(); | |||
MfaLevel? oldMfaLevel = mfaLevelModel?.OldValue?.ToObject<MfaLevel>(), | |||
newMfaLevel = mfaLevelModel?.NewValue?.ToObject<MfaLevel>(); | |||
int? oldContentFilter = contentFilterModel?.OldValue?.ToObject<int>(), | |||
newContentFilter = contentFilterModel?.NewValue?.ToObject<int>(); | |||
int? oldAfkTimeout = afkTimeoutModel?.OldValue?.ToObject<int>(discord.ApiClient.Serializer), | |||
newAfkTimeout = afkTimeoutModel?.NewValue?.ToObject<int>(discord.ApiClient.Serializer); | |||
DefaultMessageNotifications? oldDefaultMessageNotifications = defaultMessageNotificationsModel?.OldValue?.ToObject<DefaultMessageNotifications>(discord.ApiClient.Serializer), | |||
newDefaultMessageNotifications = defaultMessageNotificationsModel?.NewValue?.ToObject<DefaultMessageNotifications>(discord.ApiClient.Serializer); | |||
ulong? oldAfkChannelId = afkChannelModel?.OldValue?.ToObject<ulong>(discord.ApiClient.Serializer), | |||
newAfkChannelId = afkChannelModel?.NewValue?.ToObject<ulong>(discord.ApiClient.Serializer); | |||
string oldName = nameModel?.OldValue?.ToObject<string>(discord.ApiClient.Serializer), | |||
newName = nameModel?.NewValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
string oldRegionId = regionIdModel?.OldValue?.ToObject<string>(discord.ApiClient.Serializer), | |||
newRegionId = regionIdModel?.NewValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
string oldIconHash = iconHashModel?.OldValue?.ToObject<string>(discord.ApiClient.Serializer), | |||
newIconHash = iconHashModel?.NewValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
VerificationLevel? oldVerificationLevel = verificationLevelModel?.OldValue?.ToObject<VerificationLevel>(discord.ApiClient.Serializer), | |||
newVerificationLevel = verificationLevelModel?.NewValue?.ToObject<VerificationLevel>(discord.ApiClient.Serializer); | |||
ulong? oldOwnerId = ownerIdModel?.OldValue?.ToObject<ulong>(discord.ApiClient.Serializer), | |||
newOwnerId = ownerIdModel?.NewValue?.ToObject<ulong>(discord.ApiClient.Serializer); | |||
MfaLevel? oldMfaLevel = mfaLevelModel?.OldValue?.ToObject<MfaLevel>(discord.ApiClient.Serializer), | |||
newMfaLevel = mfaLevelModel?.NewValue?.ToObject<MfaLevel>(discord.ApiClient.Serializer); | |||
int? oldContentFilter = contentFilterModel?.OldValue?.ToObject<int>(discord.ApiClient.Serializer), | |||
newContentFilter = contentFilterModel?.NewValue?.ToObject<int>(discord.ApiClient.Serializer); | |||
IUser oldOwner = null; | |||
if (oldOwnerId != null) | |||
@@ -30,13 +30,13 @@ namespace Discord.Rest | |||
var usesModel = changes.FirstOrDefault(x => x.ChangedProperty == "uses"); | |||
var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses"); | |||
var maxAge = maxAgeModel.NewValue.ToObject<int>(); | |||
var code = codeModel.NewValue.ToObject<string>(); | |||
var temporary = temporaryModel.NewValue.ToObject<bool>(); | |||
var inviterId = inviterIdModel.NewValue.ToObject<ulong>(); | |||
var channelId = channelIdModel.NewValue.ToObject<ulong>(); | |||
var uses = usesModel.NewValue.ToObject<int>(); | |||
var maxUses = maxUsesModel.NewValue.ToObject<int>(); | |||
var maxAge = maxAgeModel.NewValue.ToObject<int>(discord.ApiClient.Serializer); | |||
var code = codeModel.NewValue.ToObject<string>(discord.ApiClient.Serializer); | |||
var temporary = temporaryModel.NewValue.ToObject<bool>(discord.ApiClient.Serializer); | |||
var inviterId = inviterIdModel.NewValue.ToObject<ulong>(discord.ApiClient.Serializer); | |||
var channelId = channelIdModel.NewValue.ToObject<ulong>(discord.ApiClient.Serializer); | |||
var uses = usesModel.NewValue.ToObject<int>(discord.ApiClient.Serializer); | |||
var maxUses = maxUsesModel.NewValue.ToObject<int>(discord.ApiClient.Serializer); | |||
var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId); | |||
var inviter = RestUser.Create(discord, inviterInfo); | |||
@@ -30,13 +30,13 @@ namespace Discord.Rest | |||
var usesModel = changes.FirstOrDefault(x => x.ChangedProperty == "uses"); | |||
var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses"); | |||
var maxAge = maxAgeModel.OldValue.ToObject<int>(); | |||
var code = codeModel.OldValue.ToObject<string>(); | |||
var temporary = temporaryModel.OldValue.ToObject<bool>(); | |||
var inviterId = inviterIdModel.OldValue.ToObject<ulong>(); | |||
var channelId = channelIdModel.OldValue.ToObject<ulong>(); | |||
var uses = usesModel.OldValue.ToObject<int>(); | |||
var maxUses = maxUsesModel.OldValue.ToObject<int>(); | |||
var maxAge = maxAgeModel.OldValue.ToObject<int>(discord.ApiClient.Serializer); | |||
var code = codeModel.OldValue.ToObject<string>(discord.ApiClient.Serializer); | |||
var temporary = temporaryModel.OldValue.ToObject<bool>(discord.ApiClient.Serializer); | |||
var inviterId = inviterIdModel.OldValue.ToObject<ulong>(discord.ApiClient.Serializer); | |||
var channelId = channelIdModel.OldValue.ToObject<ulong>(discord.ApiClient.Serializer); | |||
var uses = usesModel.OldValue.ToObject<int>(discord.ApiClient.Serializer); | |||
var maxUses = maxUsesModel.OldValue.ToObject<int>(discord.ApiClient.Serializer); | |||
var inviterInfo = log.Users.FirstOrDefault(x => x.Id == inviterId); | |||
var inviter = RestUser.Create(discord, inviterInfo); | |||
@@ -23,16 +23,16 @@ namespace Discord.Rest | |||
var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); | |||
var maxUsesModel = changes.FirstOrDefault(x => x.ChangedProperty == "max_uses"); | |||
int? oldMaxAge = maxAgeModel?.OldValue?.ToObject<int>(), | |||
newMaxAge = maxAgeModel?.NewValue?.ToObject<int>(); | |||
string oldCode = codeModel?.OldValue?.ToObject<string>(), | |||
newCode = codeModel?.NewValue?.ToObject<string>(); | |||
bool? oldTemporary = temporaryModel?.OldValue?.ToObject<bool>(), | |||
newTemporary = temporaryModel?.NewValue?.ToObject<bool>(); | |||
ulong? oldChannelId = channelIdModel?.OldValue?.ToObject<ulong>(), | |||
newChannelId = channelIdModel?.NewValue?.ToObject<ulong>(); | |||
int? oldMaxUses = maxUsesModel?.OldValue?.ToObject<int>(), | |||
newMaxUses = maxUsesModel?.NewValue?.ToObject<int>(); | |||
int? oldMaxAge = maxAgeModel?.OldValue?.ToObject<int>(discord.ApiClient.Serializer), | |||
newMaxAge = maxAgeModel?.NewValue?.ToObject<int>(discord.ApiClient.Serializer); | |||
string oldCode = codeModel?.OldValue?.ToObject<string>(discord.ApiClient.Serializer), | |||
newCode = codeModel?.NewValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
bool? oldTemporary = temporaryModel?.OldValue?.ToObject<bool>(discord.ApiClient.Serializer), | |||
newTemporary = temporaryModel?.NewValue?.ToObject<bool>(discord.ApiClient.Serializer); | |||
ulong? oldChannelId = channelIdModel?.OldValue?.ToObject<ulong>(discord.ApiClient.Serializer), | |||
newChannelId = channelIdModel?.NewValue?.ToObject<ulong>(discord.ApiClient.Serializer); | |||
int? oldMaxUses = maxUsesModel?.OldValue?.ToObject<int>(discord.ApiClient.Serializer), | |||
newMaxUses = maxUsesModel?.NewValue?.ToObject<int>(discord.ApiClient.Serializer); | |||
var before = new InviteInfo(oldMaxAge, oldCode, oldTemporary, oldChannelId, oldMaxUses); | |||
var after = new InviteInfo(newMaxAge, newCode, newTemporary, newChannelId, newMaxUses); | |||
@@ -19,7 +19,7 @@ namespace Discord.Rest | |||
{ | |||
var changes = entry.Changes; | |||
var roleInfos = changes.SelectMany(x => x.NewValue.ToObject<API.Role[]>(), | |||
var roleInfos = changes.SelectMany(x => x.NewValue.ToObject<API.Role[]>(discord.ApiClient.Serializer), | |||
(model, role) => new { model.ChangedProperty, Role = role }) | |||
.Select(x => new MemberRoleEditInfo(x.Role.Name, x.Role.Id, x.ChangedProperty == "$add")) | |||
.ToList(); | |||
@@ -24,14 +24,14 @@ namespace Discord.Rest | |||
var muteModel = changes.FirstOrDefault(x => x.ChangedProperty == "mute"); | |||
var avatarModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash"); | |||
string oldNick = nickModel?.OldValue?.ToObject<string>(), | |||
newNick = nickModel?.NewValue?.ToObject<string>(); | |||
bool? oldDeaf = deafModel?.OldValue?.ToObject<bool>(), | |||
newDeaf = deafModel?.NewValue?.ToObject<bool>(); | |||
bool? oldMute = muteModel?.OldValue?.ToObject<bool>(), | |||
newMute = muteModel?.NewValue?.ToObject<bool>(); | |||
string oldAvatar = avatarModel?.OldValue?.ToObject<string>(), | |||
newAvatar = avatarModel?.NewValue?.ToObject<string>(); | |||
string oldNick = nickModel?.OldValue?.ToObject<string>(discord.ApiClient.Serializer), | |||
newNick = nickModel?.NewValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
bool? oldDeaf = deafModel?.OldValue?.ToObject<bool>(discord.ApiClient.Serializer), | |||
newDeaf = deafModel?.NewValue?.ToObject<bool>(discord.ApiClient.Serializer); | |||
bool? oldMute = muteModel?.OldValue?.ToObject<bool>(discord.ApiClient.Serializer), | |||
newMute = muteModel?.NewValue?.ToObject<bool>(discord.ApiClient.Serializer); | |||
string oldAvatar = avatarModel?.OldValue?.ToObject<string>(discord.ApiClient.Serializer), | |||
newAvatar = avatarModel?.NewValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
var targetInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); | |||
var user = RestUser.Create(discord, targetInfo); | |||
@@ -19,17 +19,15 @@ namespace Discord.Rest | |||
var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny"); | |||
var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); | |||
var deny = denyModel.NewValue.ToObject<ulong>(); | |||
var allow = allowModel.NewValue.ToObject<ulong>(); | |||
var deny = denyModel.NewValue.ToObject<ulong>(discord.ApiClient.Serializer); | |||
var allow = allowModel.NewValue.ToObject<ulong>(discord.ApiClient.Serializer); | |||
var permissions = new OverwritePermissions(allow, deny); | |||
var id = entry.Options.OverwriteTargetId.Value; | |||
var type = entry.Options.OverwriteType; | |||
PermissionTarget target = type == "member" ? PermissionTarget.User : PermissionTarget.Role; | |||
return new OverwriteCreateAuditLogData(new Overwrite(id, target, permissions)); | |||
return new OverwriteCreateAuditLogData(new Overwrite(id, type, permissions)); | |||
} | |||
public Overwrite Overwrite { get; } | |||
@@ -27,14 +27,12 @@ namespace Discord.Rest | |||
var idModel = changes.FirstOrDefault(x => x.ChangedProperty == "id"); | |||
var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); | |||
var deny = denyModel.OldValue.ToObject<ulong>(); | |||
var type = typeModel.OldValue.ToObject<string>(); | |||
var id = idModel.OldValue.ToObject<ulong>(); | |||
var allow = allowModel.OldValue.ToObject<ulong>(); | |||
var deny = denyModel.OldValue.ToObject<ulong>(discord.ApiClient.Serializer); | |||
var type = typeModel.OldValue.ToObject<PermissionTarget>(discord.ApiClient.Serializer); | |||
var id = idModel.OldValue.ToObject<ulong>(discord.ApiClient.Serializer); | |||
var allow = allowModel.OldValue.ToObject<ulong>(discord.ApiClient.Serializer); | |||
PermissionTarget target = type == "member" ? PermissionTarget.User : PermissionTarget.Role; | |||
return new OverwriteDeleteAuditLogData(new Overwrite(id, target, new OverwritePermissions(allow, deny))); | |||
return new OverwriteDeleteAuditLogData(new Overwrite(id, type, new OverwritePermissions(allow, deny))); | |||
} | |||
public Overwrite Overwrite { get; } | |||
@@ -22,17 +22,17 @@ namespace Discord.Rest | |||
var denyModel = changes.FirstOrDefault(x => x.ChangedProperty == "deny"); | |||
var allowModel = changes.FirstOrDefault(x => x.ChangedProperty == "allow"); | |||
var beforeAllow = allowModel?.OldValue?.ToObject<ulong>(); | |||
var afterAllow = allowModel?.NewValue?.ToObject<ulong>(); | |||
var beforeDeny = denyModel?.OldValue?.ToObject<ulong>(); | |||
var afterDeny = denyModel?.OldValue?.ToObject<ulong>(); | |||
var beforeAllow = allowModel?.OldValue?.ToObject<ulong>(discord.ApiClient.Serializer); | |||
var afterAllow = allowModel?.NewValue?.ToObject<ulong>(discord.ApiClient.Serializer); | |||
var beforeDeny = denyModel?.OldValue?.ToObject<ulong>(discord.ApiClient.Serializer); | |||
var afterDeny = denyModel?.OldValue?.ToObject<ulong>(discord.ApiClient.Serializer); | |||
var beforePermissions = new OverwritePermissions(beforeAllow ?? 0, beforeDeny ?? 0); | |||
var afterPermissions = new OverwritePermissions(afterAllow ?? 0, afterDeny ?? 0); | |||
PermissionTarget target = entry.Options.OverwriteType == "member" ? PermissionTarget.User : PermissionTarget.Role; | |||
var type = entry.Options.OverwriteType; | |||
return new OverwriteUpdateAuditLogData(beforePermissions, afterPermissions, entry.Options.OverwriteTargetId.Value, target); | |||
return new OverwriteUpdateAuditLogData(beforePermissions, afterPermissions, entry.Options.OverwriteTargetId.Value, type); | |||
} | |||
public OverwritePermissions OldPermissions { get; } | |||
@@ -23,11 +23,11 @@ namespace Discord.Rest | |||
var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); | |||
var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions"); | |||
uint? colorRaw = colorModel?.NewValue?.ToObject<uint>(); | |||
bool? mentionable = mentionableModel?.NewValue?.ToObject<bool>(); | |||
bool? hoist = hoistModel?.NewValue?.ToObject<bool>(); | |||
string name = nameModel?.NewValue?.ToObject<string>(); | |||
ulong? permissionsRaw = permissionsModel?.NewValue?.ToObject<ulong>(); | |||
uint? colorRaw = colorModel?.NewValue?.ToObject<uint>(discord.ApiClient.Serializer); | |||
bool? mentionable = mentionableModel?.NewValue?.ToObject<bool>(discord.ApiClient.Serializer); | |||
bool? hoist = hoistModel?.NewValue?.ToObject<bool>(discord.ApiClient.Serializer); | |||
string name = nameModel?.NewValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
ulong? permissionsRaw = permissionsModel?.NewValue?.ToObject<ulong>(discord.ApiClient.Serializer); | |||
Color? color = null; | |||
GuildPermissions? permissions = null; | |||
@@ -23,11 +23,11 @@ namespace Discord.Rest | |||
var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); | |||
var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions"); | |||
uint? colorRaw = colorModel?.OldValue?.ToObject<uint>(); | |||
bool? mentionable = mentionableModel?.OldValue?.ToObject<bool>(); | |||
bool? hoist = hoistModel?.OldValue?.ToObject<bool>(); | |||
string name = nameModel?.OldValue?.ToObject<string>(); | |||
ulong? permissionsRaw = permissionsModel?.OldValue?.ToObject<ulong>(); | |||
uint? colorRaw = colorModel?.OldValue?.ToObject<uint>(discord.ApiClient.Serializer); | |||
bool? mentionable = mentionableModel?.OldValue?.ToObject<bool>(discord.ApiClient.Serializer); | |||
bool? hoist = hoistModel?.OldValue?.ToObject<bool>(discord.ApiClient.Serializer); | |||
string name = nameModel?.OldValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
ulong? permissionsRaw = permissionsModel?.OldValue?.ToObject<ulong>(discord.ApiClient.Serializer); | |||
Color? color = null; | |||
GuildPermissions? permissions = null; | |||
@@ -24,16 +24,16 @@ namespace Discord.Rest | |||
var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); | |||
var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions"); | |||
uint? oldColorRaw = colorModel?.OldValue?.ToObject<uint>(), | |||
newColorRaw = colorModel?.NewValue?.ToObject<uint>(); | |||
bool? oldMentionable = mentionableModel?.OldValue?.ToObject<bool>(), | |||
newMentionable = mentionableModel?.NewValue?.ToObject<bool>(); | |||
bool? oldHoist = hoistModel?.OldValue?.ToObject<bool>(), | |||
newHoist = hoistModel?.NewValue?.ToObject<bool>(); | |||
string oldName = nameModel?.OldValue?.ToObject<string>(), | |||
newName = nameModel?.NewValue?.ToObject<string>(); | |||
ulong? oldPermissionsRaw = permissionsModel?.OldValue?.ToObject<ulong>(), | |||
newPermissionsRaw = permissionsModel?.OldValue?.ToObject<ulong>(); | |||
uint? oldColorRaw = colorModel?.OldValue?.ToObject<uint>(discord.ApiClient.Serializer), | |||
newColorRaw = colorModel?.NewValue?.ToObject<uint>(discord.ApiClient.Serializer); | |||
bool? oldMentionable = mentionableModel?.OldValue?.ToObject<bool>(discord.ApiClient.Serializer), | |||
newMentionable = mentionableModel?.NewValue?.ToObject<bool>(discord.ApiClient.Serializer); | |||
bool? oldHoist = hoistModel?.OldValue?.ToObject<bool>(discord.ApiClient.Serializer), | |||
newHoist = hoistModel?.NewValue?.ToObject<bool>(discord.ApiClient.Serializer); | |||
string oldName = nameModel?.OldValue?.ToObject<string>(discord.ApiClient.Serializer), | |||
newName = nameModel?.NewValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
ulong? oldPermissionsRaw = permissionsModel?.OldValue?.ToObject<ulong>(discord.ApiClient.Serializer), | |||
newPermissionsRaw = permissionsModel?.OldValue?.ToObject<ulong>(discord.ApiClient.Serializer); | |||
Color? oldColor = null, | |||
newColor = null; | |||
@@ -23,9 +23,9 @@ namespace Discord.Rest | |||
var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); | |||
var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); | |||
var channelId = channelIdModel.NewValue.ToObject<ulong>(); | |||
var type = typeModel.NewValue.ToObject<WebhookType>(); | |||
var name = nameModel.NewValue.ToObject<string>(); | |||
var channelId = channelIdModel.NewValue.ToObject<ulong>(discord.ApiClient.Serializer); | |||
var type = typeModel.NewValue.ToObject<WebhookType>(discord.ApiClient.Serializer); | |||
var name = nameModel.NewValue.ToObject<string>(discord.ApiClient.Serializer); | |||
var webhookInfo = log.Webhooks?.FirstOrDefault(x => x.Id == entry.TargetId); | |||
var webhook = RestWebhook.Create(discord, (IGuild)null, webhookInfo); | |||
@@ -29,10 +29,10 @@ namespace Discord.Rest | |||
var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); | |||
var avatarHashModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash"); | |||
var channelId = channelIdModel.OldValue.ToObject<ulong>(); | |||
var type = typeModel.OldValue.ToObject<WebhookType>(); | |||
var name = nameModel.OldValue.ToObject<string>(); | |||
var avatarHash = avatarHashModel?.OldValue?.ToObject<string>(); | |||
var channelId = channelIdModel.OldValue.ToObject<ulong>(discord.ApiClient.Serializer); | |||
var type = typeModel.OldValue.ToObject<WebhookType>(discord.ApiClient.Serializer); | |||
var name = nameModel.OldValue.ToObject<string>(discord.ApiClient.Serializer); | |||
var avatarHash = avatarHashModel?.OldValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
return new WebhookDeleteAuditLogData(entry.TargetId.Value, channelId, type, name, avatarHash); | |||
} | |||
@@ -26,18 +26,18 @@ namespace Discord.Rest | |||
var channelIdModel = changes.FirstOrDefault(x => x.ChangedProperty == "channel_id"); | |||
var avatarHashModel = changes.FirstOrDefault(x => x.ChangedProperty == "avatar_hash"); | |||
var oldName = nameModel?.OldValue?.ToObject<string>(); | |||
var oldChannelId = channelIdModel?.OldValue?.ToObject<ulong>(); | |||
var oldAvatar = avatarHashModel?.OldValue?.ToObject<string>(); | |||
var oldName = nameModel?.OldValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
var oldChannelId = channelIdModel?.OldValue?.ToObject<ulong>(discord.ApiClient.Serializer); | |||
var oldAvatar = avatarHashModel?.OldValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
var before = new WebhookInfo(oldName, oldChannelId, oldAvatar); | |||
var newName = nameModel?.NewValue?.ToObject<string>(); | |||
var newChannelId = channelIdModel?.NewValue?.ToObject<ulong>(); | |||
var newAvatar = avatarHashModel?.NewValue?.ToObject<string>(); | |||
var newName = nameModel?.NewValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
var newChannelId = channelIdModel?.NewValue?.ToObject<ulong>(discord.ApiClient.Serializer); | |||
var newAvatar = avatarHashModel?.NewValue?.ToObject<string>(discord.ApiClient.Serializer); | |||
var after = new WebhookInfo(newName, newChannelId, newAvatar); | |||
var webhookInfo = log.Webhooks?.FirstOrDefault(x => x.Id == entry.TargetId); | |||
var webhook = RestWebhook.Create(discord, (IGuild)null, webhookInfo); | |||
var webhook = webhookInfo != null ? RestWebhook.Create(discord, (IGuild)null, webhookInfo) : null; | |||
return new WebhookUpdateAuditLogData(webhook, before, after); | |||
} | |||
@@ -1,4 +1,5 @@ | |||
using System.Linq; | |||
using System; | |||
using System.Linq; | |||
using Model = Discord.API.AuditLog; | |||
using EntryModel = Discord.API.AuditLogEntry; | |||
@@ -26,6 +27,8 @@ namespace Discord.Rest | |||
return new RestAuditLogEntry(discord, fullLog, model, user); | |||
} | |||
/// <inheritdoc/> | |||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||
/// <inheritdoc/> | |||
public ActionType Action { get; } | |||
/// <inheritdoc/> | |||
@@ -10,7 +10,7 @@ using Model = Discord.API.Channel; | |||
namespace Discord.Rest | |||
{ | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class RestDMChannel : RestChannel, IDMChannel, IRestPrivateChannel, IRestMessageChannel, IUpdateable | |||
public class RestDMChannel : RestChannel, IDMChannel, IRestPrivateChannel, IRestMessageChannel | |||
{ | |||
public RestUser CurrentUser { get; private set; } | |||
public RestUser Recipient { get; private set; } | |||
@@ -11,7 +11,7 @@ using Model = Discord.API.Channel; | |||
namespace Discord.Rest | |||
{ | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class RestGroupChannel : RestChannel, IGroupChannel, IRestPrivateChannel, IRestMessageChannel, IRestAudioChannel, IUpdateable | |||
public class RestGroupChannel : RestChannel, IGroupChannel, IRestPrivateChannel, IRestMessageChannel, IRestAudioChannel | |||
{ | |||
private string _iconId; | |||
private ImmutableDictionary<ulong, RestGroupUser> _users; | |||
@@ -7,7 +7,7 @@ using Model = Discord.API.Channel; | |||
namespace Discord.Rest | |||
{ | |||
public class RestGuildChannel : RestChannel, IGuildChannel, IUpdateable | |||
public class RestGuildChannel : RestChannel, IGuildChannel | |||
{ | |||
private ImmutableArray<Overwrite> _overwrites; | |||
@@ -168,7 +168,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(); | |||
} | |||
public async Task<RestVoiceChannel> GetVoiceChannelAsync(ulong id, RequestOptions options = null) | |||
{ | |||
@@ -178,12 +178,12 @@ 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(); | |||
} | |||
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(); | |||
} | |||
public async Task<RestVoiceChannel> GetAFKChannelAsync(RequestOptions options = null) | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Diagnostics; | |||
using System.Threading.Tasks; | |||
using Model = Discord.API.UserGuild; | |||
@@ -6,7 +6,7 @@ using Model = Discord.API.UserGuild; | |||
namespace Discord.Rest | |||
{ | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class RestUserGuild : RestEntity<ulong>, ISnowflakeEntity, IUserGuild | |||
public class RestUserGuild : RestEntity<ulong>, IUserGuild | |||
{ | |||
private string _iconId; | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.Diagnostics; | |||
@@ -9,7 +9,7 @@ using Model = Discord.API.GuildMember; | |||
namespace Discord.Rest | |||
{ | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class RestGuildUser : RestUser, IGuildUser, IUpdateable | |||
public class RestGuildUser : RestUser, IGuildUser | |||
{ | |||
private long? _joinedAtTicks; | |||
private ImmutableArray<ulong> _roleIds; | |||
@@ -131,14 +131,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; } | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Collections.Concurrent; | |||
#if DEBUG_LIMITS | |||
using System.Diagnostics; | |||
@@ -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; | |||
@@ -115,7 +115,7 @@ namespace Discord.Net.Queue | |||
foreach (var bucket in _buckets.Select(x => x.Value)) | |||
{ | |||
if ((now - bucket.LastAttemptAt).TotalMinutes > 1.0) | |||
_buckets.TryRemove(bucket.Id, out RequestBucket ignored); | |||
_buckets.TryRemove(bucket.Id, out _); | |||
} | |||
await Task.Delay(60000, _cancelToken.Token); //Runs each minute | |||
} | |||
@@ -15,7 +15,7 @@ namespace Discord.Net | |||
internal RateLimitInfo(Dictionary<string, string> headers) | |||
{ | |||
IsGlobal = headers.TryGetValue("X-RateLimit-Global", out string temp) && | |||
bool.TryParse(temp, out var isGlobal) ? isGlobal : false; | |||
bool.TryParse(temp, out var isGlobal) && isGlobal; | |||
Limit = headers.TryGetValue("X-RateLimit-Limit", out temp) && | |||
int.TryParse(temp, out var limit) ? limit : (int?)null; | |||
Remaining = headers.TryGetValue("X-RateLimit-Remaining", out temp) && | |||
@@ -16,7 +16,7 @@ using System.Collections.Generic; | |||
namespace Discord.Audio | |||
{ | |||
//TODO: Add audio reconnecting | |||
internal partial class AudioClient : IAudioClient, IDisposable | |||
internal partial class AudioClient : IAudioClient | |||
{ | |||
internal struct StreamPair | |||
{ | |||
@@ -65,7 +65,7 @@ namespace Discord.Audio | |||
ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider); | |||
ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); | |||
ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync($"Sent Discovery").ConfigureAwait(false); | |||
ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync("Sent Discovery").ConfigureAwait(false); | |||
//ApiClient.SentData += async bytes => await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false); | |||
ApiClient.ReceivedEvent += ProcessMessageAsync; | |||
ApiClient.ReceivedPacket += ProcessPacketAsync; | |||
@@ -131,7 +131,7 @@ namespace Discord.Audio | |||
await keepaliveTask.ConfigureAwait(false); | |||
_keepaliveTask = null; | |||
while (_heartbeatTimes.TryDequeue(out long time)) { } | |||
while (_heartbeatTimes.TryDequeue(out _)) { } | |||
_lastMessageTime = 0; | |||
await ClearInputStreamsAsync().ConfigureAwait(false); | |||
@@ -292,7 +292,7 @@ namespace Discord.Audio | |||
{ | |||
if (packet.Length != 70) | |||
{ | |||
await _audioLogger.DebugAsync($"Malformed Packet").ConfigureAwait(false); | |||
await _audioLogger.DebugAsync("Malformed Packet").ConfigureAwait(false); | |||
return; | |||
} | |||
string ip; | |||
@@ -304,7 +304,7 @@ namespace Discord.Audio | |||
} | |||
catch (Exception ex) | |||
{ | |||
await _audioLogger.DebugAsync($"Malformed Packet", ex).ConfigureAwait(false); | |||
await _audioLogger.DebugAsync("Malformed Packet", ex).ConfigureAwait(false); | |||
return; | |||
} | |||
@@ -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; | |||
@@ -344,7 +344,7 @@ namespace Discord.Audio | |||
{ | |||
if (!RTPReadStream.TryReadSsrc(packet, 0, out var ssrc)) | |||
{ | |||
await _audioLogger.DebugAsync($"Malformed Frame").ConfigureAwait(false); | |||
await _audioLogger.DebugAsync("Malformed Frame").ConfigureAwait(false); | |||
return; | |||
} | |||
if (!_ssrcMap.TryGetValue(ssrc, out var userId)) | |||
@@ -363,7 +363,7 @@ namespace Discord.Audio | |||
} | |||
catch (Exception ex) | |||
{ | |||
await _audioLogger.DebugAsync($"Malformed Frame", ex).ConfigureAwait(false); | |||
await _audioLogger.DebugAsync("Malformed Frame", ex).ConfigureAwait(false); | |||
return; | |||
} | |||
//await _audioLogger.DebugAsync($"Received {packet.Length} bytes from user {userId}").ConfigureAwait(false); | |||
@@ -372,7 +372,7 @@ namespace Discord.Audio | |||
} | |||
catch (Exception ex) | |||
{ | |||
await _audioLogger.WarningAsync($"Failed to process UDP packet", ex).ConfigureAwait(false); | |||
await _audioLogger.WarningAsync("Failed to process UDP packet", ex).ConfigureAwait(false); | |||
return; | |||
} | |||
} | |||
@@ -116,7 +116,7 @@ namespace Discord.Audio.Streams | |||
timestamp += OpusEncoder.FrameSamplesPerChannel; | |||
} | |||
#if DEBUG | |||
var _ = _logger?.DebugAsync($"Buffer underrun"); | |||
var _ = _logger?.DebugAsync("Buffer underrun"); | |||
#endif | |||
} | |||
} | |||
@@ -140,7 +140,7 @@ namespace Discord.Audio.Streams | |||
if (!_bufferPool.TryDequeue(out byte[] buffer)) | |||
{ | |||
#if DEBUG | |||
var _ = _logger?.DebugAsync($"Buffer overflow"); //Should never happen because of the queueLock | |||
var _ = _logger?.DebugAsync("Buffer overflow"); //Should never happen because of the queueLock | |||
#endif | |||
return; | |||
} | |||
@@ -149,7 +149,7 @@ namespace Discord.Audio.Streams | |||
if (!_isPreloaded && _queuedFrames.Count == _queueLength) | |||
{ | |||
#if DEBUG | |||
var _ = _logger?.DebugAsync($"Preloaded"); | |||
var _ = _logger?.DebugAsync("Preloaded"); | |||
#endif | |||
_isPreloaded = true; | |||
} | |||
@@ -169,8 +169,8 @@ namespace Discord.Audio.Streams | |||
{ | |||
do | |||
cancelToken.ThrowIfCancellationRequested(); | |||
while (_queuedFrames.TryDequeue(out Frame ignored)); | |||
while (_queuedFrames.TryDequeue(out _)); | |||
return Task.Delay(0); | |||
} | |||
} | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
@@ -72,7 +72,7 @@ namespace Discord.WebSocket | |||
switch (channel) | |||
{ | |||
case SocketDMChannel dmChannel: | |||
_dmChannels.TryRemove(dmChannel.Recipient.Id, out var ignored); | |||
_dmChannels.TryRemove(dmChannel.Recipient.Id, out _); | |||
break; | |||
case SocketGroupChannel groupChannel: | |||
_groupChannels.TryRemove(id); | |||
@@ -13,11 +13,11 @@ namespace Discord.WebSocket | |||
{ | |||
private readonly DiscordSocketConfig _baseConfig; | |||
private readonly SemaphoreSlim _connectionGroupLock; | |||
private readonly Dictionary<int, int> _shardIdsToIndex; | |||
private readonly bool _automaticShards; | |||
private int[] _shardIds; | |||
private Dictionary<int, int> _shardIdsToIndex; | |||
private DiscordSocketClient[] _shards; | |||
private int _totalShards; | |||
private bool _automaticShards; | |||
/// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | |||
public override int Latency { get => GetLatency(); protected set { } } | |||
@@ -25,8 +25,8 @@ namespace Discord.WebSocket | |||
public override IActivity Activity { get => _shards[0].Activity; protected set { } } | |||
internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | |||
public override IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount()); | |||
public override IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(() => GetPrivateChannelCount()); | |||
public override IReadOnlyCollection<SocketGuild> Guilds => GetGuilds().ToReadOnlyCollection(GetGuildCount); | |||
public override IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(GetPrivateChannelCount); | |||
public IReadOnlyCollection<DiscordSocketClient> Shards => _shards; | |||
public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _shards[0].VoiceRegions; | |||
@@ -26,9 +26,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; | |||
@@ -63,9 +63,9 @@ namespace Discord.WebSocket | |||
public override IReadOnlyCollection<SocketGuild> Guilds => State.Guilds; | |||
public override IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => State.PrivateChannels; | |||
public IReadOnlyCollection<SocketDMChannel> DMChannels | |||
=> State.PrivateChannels.Select(x => x as SocketDMChannel).Where(x => x != null).ToImmutableArray(); | |||
=> State.PrivateChannels.OfType<SocketDMChannel>().ToImmutableArray(); | |||
public IReadOnlyCollection<SocketGroupChannel> GroupChannels | |||
=> State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); | |||
=> State.PrivateChannels.OfType<SocketGroupChannel>().ToImmutableArray(); | |||
public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection(); | |||
/// <summary> Creates a new REST/WebSocket discord client. </summary> | |||
@@ -207,7 +207,7 @@ namespace Discord.WebSocket | |||
await heartbeatTask.ConfigureAwait(false); | |||
_heartbeatTask = null; | |||
while (_heartbeatTimes.TryDequeue(out long time)) { } | |||
while (_heartbeatTimes.TryDequeue(out _)) { } | |||
_lastMessageTime = 0; | |||
await _gatewayLogger.DebugAsync("Waiting for guild downloader").ConfigureAwait(false); | |||
@@ -218,7 +218,7 @@ namespace Discord.WebSocket | |||
//Clear large guild queue | |||
await _gatewayLogger.DebugAsync("Clearing large guild queue").ConfigureAwait(false); | |||
while (_largeGuilds.TryDequeue(out ulong guildId)) { } | |||
while (_largeGuilds.TryDequeue(out _)) { } | |||
//Raise virtual GUILD_UNAVAILABLEs | |||
await _gatewayLogger.DebugAsync("Raising virtual GuildUnavailables").ConfigureAwait(false); | |||
@@ -351,7 +351,7 @@ namespace Discord.WebSocket | |||
var gameModel = new GameModel(); | |||
// Discord only accepts rich presence over RPC, don't even bother building a payload | |||
if (Activity is RichGame game) | |||
if (Activity is RichGame) | |||
throw new NotSupportedException("Outgoing Rich Presences are not supported"); | |||
if (Activity != null) | |||
@@ -508,7 +508,7 @@ namespace Discord.WebSocket | |||
{ | |||
type = "GUILD_AVAILABLE"; | |||
_lastGuildAvailableTime = Environment.TickCount; | |||
await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_AVAILABLE)").ConfigureAwait(false); | |||
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_AVAILABLE)").ConfigureAwait(false); | |||
var guild = State.GetGuild(data.Id); | |||
if (guild != null) | |||
@@ -533,7 +533,7 @@ namespace Discord.WebSocket | |||
} | |||
else | |||
{ | |||
await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); | |||
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); | |||
var guild = AddGuild(data, State); | |||
if (guild != null) | |||
@@ -614,7 +614,7 @@ namespace Discord.WebSocket | |||
if (data.Unavailable == true) | |||
{ | |||
type = "GUILD_UNAVAILABLE"; | |||
await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_UNAVAILABLE)").ConfigureAwait(false); | |||
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UNAVAILABLE)").ConfigureAwait(false); | |||
var guild = State.GetGuild(data.Id); | |||
if (guild != null) | |||
@@ -630,7 +630,7 @@ namespace Discord.WebSocket | |||
} | |||
else | |||
{ | |||
await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_DELETE)").ConfigureAwait(false); | |||
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_DELETE)").ConfigureAwait(false); | |||
var guild = RemoveGuild(data.Id); | |||
if (guild != null) | |||
@@ -1630,7 +1630,7 @@ namespace Discord.WebSocket | |||
var guild = State.RemoveGuild(id); | |||
if (guild != null) | |||
{ | |||
foreach (var channel in guild.Channels) | |||
foreach (var _ in guild.Channels) | |||
State.RemoveChannel(id); | |||
foreach (var user in guild.Users) | |||
user.GlobalUser.RemoveRef(this); | |||
@@ -1683,7 +1683,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; | |||
@@ -188,7 +188,7 @@ namespace Discord.Audio | |||
ConnectionState = ConnectionState.Connected; | |||
} | |||
catch (Exception) | |||
catch | |||
{ | |||
await DisconnectInternalAsync().ConfigureAwait(false); | |||
throw; | |||
@@ -97,7 +97,7 @@ namespace Discord.WebSocket | |||
if (id == Recipient.Id) | |||
return Recipient; | |||
else if (id == Discord.CurrentUser.Id) | |||
return Discord.CurrentUser as SocketSelfUser; | |||
return Discord.CurrentUser; | |||
else | |||
return null; | |||
} | |||
@@ -18,10 +18,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 ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates; | |||
public string Name { get; private set; } | |||
@@ -129,7 +129,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); | |||
@@ -143,7 +143,7 @@ namespace Discord.WebSocket | |||
if (_users.TryRemove(id, out SocketGroupUser user)) | |||
{ | |||
user.GlobalUser.RemoveRef(Discord); | |||
return user as SocketGroupUser; | |||
return user; | |||
} | |||
return null; | |||
} | |||
@@ -43,7 +43,7 @@ namespace Discord.WebSocket | |||
public Task ModifyAsync(Action<VoiceChannelProperties> func, RequestOptions options = null) | |||
=> ChannelHelper.ModifyAsync(this, Discord, func, options); | |||
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); | |||
} | |||
@@ -91,11 +91,11 @@ namespace Discord.WebSocket | |||
} | |||
} | |||
public IReadOnlyCollection<SocketTextChannel> TextChannels | |||
=> Channels.Select(x => x as SocketTextChannel).Where(x => x != null).ToImmutableArray(); | |||
=> Channels.OfType<SocketTextChannel>().ToImmutableArray(); | |||
public IReadOnlyCollection<SocketVoiceChannel> VoiceChannels | |||
=> Channels.Select(x => x as SocketVoiceChannel).Where(x => x != null).ToImmutableArray(); | |||
=> Channels.OfType<SocketVoiceChannel>().ToImmutableArray(); | |||
public IReadOnlyCollection<SocketCategoryChannel> CategoryChannels | |||
=> Channels.Select(x => x as SocketCategoryChannel).Where(x => x != null).ToImmutableArray(); | |||
=> Channels.OfType<SocketCategoryChannel>().ToImmutableArray(); | |||
public SocketGuildUser CurrentUser => _members.TryGetValue(Discord.CurrentUser.Id, out SocketGuildUser member) ? member : null; | |||
public SocketRole EveryoneRole => GetRole(Id); | |||
public IReadOnlyCollection<SocketGuildChannel> Channels | |||
@@ -563,7 +563,7 @@ namespace Discord.WebSocket | |||
await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); | |||
} | |||
catch (Exception) | |||
catch | |||
{ | |||
await DisconnectAudioInternalAsync().ConfigureAwait(false); | |||
throw; | |||
@@ -580,7 +580,7 @@ namespace Discord.WebSocket | |||
throw new TimeoutException(); | |||
return await promise.Task.ConfigureAwait(false); | |||
} | |||
catch (Exception) | |||
catch | |||
{ | |||
await DisconnectAudioAsync().ConfigureAwait(false); | |||
throw; | |||
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
@@ -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 msg); | |||
_messages.TryRemove(msgId, out _); | |||
} | |||
} | |||
@@ -12,12 +12,12 @@ 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 List<SocketReaction> _reactions = new List<SocketReaction>(); | |||
public override bool IsTTS => _isTTS; | |||
public override bool IsPinned => _isPinned; | |||
@@ -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")); | |||
} | |||
} | |||
} |