diff --git a/src/Discord.Net/API/DiscordRawClient.cs b/src/Discord.Net/API/DiscordRawClient.cs index a251af8c5..effc5fde2 100644 --- a/src/Discord.Net/API/DiscordRawClient.cs +++ b/src/Discord.Net/API/DiscordRawClient.cs @@ -45,11 +45,13 @@ namespace Discord.API } _restClient = restClientProvider(DiscordConfig.ClientAPIUrl, cancelToken); + _restClient.SetHeader("accept", "*/*"); _restClient.SetHeader("authorization", authToken); _restClient.SetHeader("user-agent", DiscordConfig.UserAgent); _requestQueue = new RequestQueue(_restClient); _serializer = new JsonSerializer(); + _serializer.Converters.Add(new OptionalConverter()); _serializer.Converters.Add(new ChannelTypeConverter()); _serializer.Converters.Add(new ImageConverter()); _serializer.Converters.Add(new NullableUInt64Converter()); @@ -59,58 +61,59 @@ namespace Discord.API _serializer.Converters.Add(new UInt64Converter()); _serializer.Converters.Add(new UInt64EntityConverter()); _serializer.Converters.Add(new UserStatusConverter()); + _serializer.ContractResolver = new OptionalContractResolver(); } //Core public Task Send(string method, string endpoint, GlobalBucket bucket = GlobalBucket.General) - => SendInternal(method, endpoint, null, bucket); + => SendInternal(method, endpoint, null, true, bucket); public Task Send(string method, string endpoint, object payload, GlobalBucket bucket = GlobalBucket.General) - => SendInternal(method, endpoint, payload, bucket); + => SendInternal(method, endpoint, payload, true, bucket); public Task Send(string method, string endpoint, Stream file, IReadOnlyDictionary multipartArgs, GlobalBucket bucket = GlobalBucket.General) - => SendInternal(method, endpoint, multipartArgs, bucket); + => SendInternal(method, endpoint, multipartArgs, true, bucket); public async Task Send(string method, string endpoint, GlobalBucket bucket = GlobalBucket.General) where TResponse : class - => Deserialize(await SendInternal(method, endpoint, null, bucket).ConfigureAwait(false)); + => Deserialize(await SendInternal(method, endpoint, null, false, bucket).ConfigureAwait(false)); public async Task Send(string method, string endpoint, object payload, GlobalBucket bucket = GlobalBucket.General) where TResponse : class - => Deserialize(await SendInternal(method, endpoint, payload, bucket).ConfigureAwait(false)); + => Deserialize(await SendInternal(method, endpoint, payload, false, bucket).ConfigureAwait(false)); public async Task Send(string method, string endpoint, Stream file, IReadOnlyDictionary multipartArgs, GlobalBucket bucket = GlobalBucket.General) where TResponse : class - => Deserialize(await SendInternal(method, endpoint, multipartArgs, bucket).ConfigureAwait(false)); + => Deserialize(await SendInternal(method, endpoint, multipartArgs, false, bucket).ConfigureAwait(false)); public Task Send(string method, string endpoint, GuildBucket bucket, ulong guildId) - => SendInternal(method, endpoint, null, bucket, guildId); + => SendInternal(method, endpoint, null, true, bucket, guildId); public Task Send(string method, string endpoint, object payload, GuildBucket bucket, ulong guildId) - => SendInternal(method, endpoint, payload, bucket, guildId); + => SendInternal(method, endpoint, payload, true, bucket, guildId); public Task Send(string method, string endpoint, Stream file, IReadOnlyDictionary multipartArgs, GuildBucket bucket, ulong guildId) - => SendInternal(method, endpoint, multipartArgs, bucket, guildId); + => SendInternal(method, endpoint, multipartArgs, true, bucket, guildId); public async Task Send(string method, string endpoint, GuildBucket bucket, ulong guildId) where TResponse : class - => Deserialize(await SendInternal(method, endpoint, null, bucket, guildId).ConfigureAwait(false)); + => Deserialize(await SendInternal(method, endpoint, null, false, bucket, guildId).ConfigureAwait(false)); public async Task Send(string method, string endpoint, object payload, GuildBucket bucket, ulong guildId) where TResponse : class - => Deserialize(await SendInternal(method, endpoint, payload, bucket, guildId).ConfigureAwait(false)); + => Deserialize(await SendInternal(method, endpoint, payload, false, bucket, guildId).ConfigureAwait(false)); public async Task Send(string method, string endpoint, Stream file, IReadOnlyDictionary multipartArgs, GuildBucket bucket, ulong guildId) where TResponse : class - => Deserialize(await SendInternal(method, endpoint, multipartArgs, bucket, guildId).ConfigureAwait(false)); + => Deserialize(await SendInternal(method, endpoint, multipartArgs, false, bucket, guildId).ConfigureAwait(false)); - private Task SendInternal(string method, string endpoint, object payload, GlobalBucket bucket) - => SendInternal(method, endpoint, payload, BucketGroup.Global, (int)bucket, 0); - private Task SendInternal(string method, string endpoint, object payload, GuildBucket bucket, ulong guildId) - => SendInternal(method, endpoint, payload, BucketGroup.Guild, (int)bucket, guildId); - private Task SendInternal(string method, string endpoint, IReadOnlyDictionary multipartArgs, GlobalBucket bucket) - => SendInternal(method, endpoint, multipartArgs, BucketGroup.Global, (int)bucket, 0); - private Task SendInternal(string method, string endpoint, IReadOnlyDictionary multipartArgs, GuildBucket bucket, ulong guildId) - => SendInternal(method, endpoint, multipartArgs, BucketGroup.Guild, (int)bucket, guildId); + private Task SendInternal(string method, string endpoint, object payload, bool headerOnly, GlobalBucket bucket) + => SendInternal(method, endpoint, payload, headerOnly, BucketGroup.Global, (int)bucket, 0); + private Task SendInternal(string method, string endpoint, object payload, bool headerOnly, GuildBucket bucket, ulong guildId) + => SendInternal(method, endpoint, payload, headerOnly, BucketGroup.Guild, (int)bucket, guildId); + private Task SendInternal(string method, string endpoint, IReadOnlyDictionary multipartArgs, bool headerOnly, GlobalBucket bucket) + => SendInternal(method, endpoint, multipartArgs, headerOnly, BucketGroup.Global, (int)bucket, 0); + private Task SendInternal(string method, string endpoint, IReadOnlyDictionary multipartArgs, bool headerOnly, GuildBucket bucket, ulong guildId) + => SendInternal(method, endpoint, multipartArgs, headerOnly, BucketGroup.Guild, (int)bucket, guildId); - private async Task SendInternal(string method, string endpoint, object payload, BucketGroup group, int bucketId, ulong guildId) + private async Task SendInternal(string method, string endpoint, object payload, bool headerOnly, BucketGroup group, int bucketId, ulong guildId) { var stopwatch = Stopwatch.StartNew(); string json = null; if (payload != null) json = Serialize(payload); - var responseStream = await _requestQueue.Send(new RestRequest(method, endpoint, json), group, bucketId, guildId).ConfigureAwait(false); - int bytes = (int)responseStream.Length; + var responseStream = await _requestQueue.Send(new RestRequest(method, endpoint, json, headerOnly), group, bucketId, guildId).ConfigureAwait(false); + int bytes = headerOnly ? 0 : (int)responseStream.Length; stopwatch.Stop(); double milliseconds = ToMilliseconds(stopwatch); @@ -118,11 +121,11 @@ namespace Discord.API return responseStream; } - private async Task SendInternal(string method, string endpoint, IReadOnlyDictionary multipartArgs, BucketGroup group, int bucketId, ulong guildId) + private async Task SendInternal(string method, string endpoint, IReadOnlyDictionary multipartArgs, bool headerOnly, BucketGroup group, int bucketId, ulong guildId) { var stopwatch = Stopwatch.StartNew(); - var responseStream = await _requestQueue.Send(new RestRequest(method, endpoint, multipartArgs), group, bucketId, guildId).ConfigureAwait(false); - int bytes = (int)responseStream.Length; + var responseStream = await _requestQueue.Send(new RestRequest(method, endpoint, multipartArgs, headerOnly), group, bucketId, guildId).ConfigureAwait(false); + int bytes = headerOnly ? 0 : (int)responseStream.Length; stopwatch.Stop(); double milliseconds = ToMilliseconds(stopwatch); @@ -448,11 +451,11 @@ namespace Discord.API if (args.Limit <= 0) throw new ArgumentOutOfRangeException(nameof(args.Limit)); if (args.Offset < 0) throw new ArgumentOutOfRangeException(nameof(args.Offset)); - int limit = args.Limit ?? int.MaxValue; + int limit = args.Limit.IsSpecified ? args.Limit.Value : int.MaxValue; int offset = args.Offset; List result; - if (args.Limit != null) + if (args.Limit.IsSpecified) result = new List((limit + DiscordConfig.MaxUsersPerBatch - 1) / DiscordConfig.MaxUsersPerBatch); else result = new List(); @@ -488,13 +491,13 @@ namespace Discord.API await Send("DELETE", $"guilds/{guildId}/members/{userId}").ConfigureAwait(false); } - public async Task ModifyGuildMember(ulong guildId, ulong userId, ModifyGuildMemberParams args) + public async Task ModifyGuildMember(ulong guildId, ulong userId, ModifyGuildMemberParams args) { if (args == null) throw new ArgumentNullException(nameof(args)); if (guildId == 0) throw new ArgumentOutOfRangeException(nameof(guildId)); if (userId == 0) throw new ArgumentOutOfRangeException(nameof(userId)); - return await Send("PATCH", $"guilds/{guildId}/members/{userId}", args).ConfigureAwait(false); + await Send("PATCH", $"guilds/{guildId}/members/{userId}", args).ConfigureAwait(false); } //Guild Roles diff --git a/src/Discord.Net/API/IOptional.cs b/src/Discord.Net/API/IOptional.cs new file mode 100644 index 000000000..51d4f5271 --- /dev/null +++ b/src/Discord.Net/API/IOptional.cs @@ -0,0 +1,8 @@ +namespace Discord.API +{ + public interface IOptional + { + object Value { get; } + bool IsSpecified { get; } + } +} diff --git a/src/Discord.Net/API/Optional.cs b/src/Discord.Net/API/Optional.cs new file mode 100644 index 000000000..172d0ecab --- /dev/null +++ b/src/Discord.Net/API/Optional.cs @@ -0,0 +1,26 @@ +namespace Discord.API +{ + public struct Optional : IOptional + { + /// Gets the value for this paramter, or default(T) if unspecified. + public T Value { get; } + /// Returns true if this value has been specified. + public bool IsSpecified { get; } + + object IOptional.Value => Value; + + /// Creates a new Parameter with the provided value. + public Optional(T value) + { + Value = value; + IsSpecified = true; + } + + /// Implicitly creates a new Parameter from an existing value. + public static implicit operator Optional(T value) => new Optional(value); + /// Implicitly creates a new Parameter from an existing value. + public static implicit operator T(Optional param) => param.Value; + + public override string ToString() => IsSpecified ? (Value?.ToString() ?? null) : null; + } +} diff --git a/src/Discord.Net/API/Rest/CreateChannelInviteParams.cs b/src/Discord.Net/API/Rest/CreateChannelInviteParams.cs index 570259867..e579a98d8 100644 --- a/src/Discord.Net/API/Rest/CreateChannelInviteParams.cs +++ b/src/Discord.Net/API/Rest/CreateChannelInviteParams.cs @@ -5,12 +5,12 @@ namespace Discord.API.Rest public class CreateChannelInviteParams { [JsonProperty("max_age")] - public int MaxAge { get; set; } = 86400; //24 Hours + public Optional MaxAge { get; set; } [JsonProperty("max_uses")] - public int MaxUses { get; set; } = 0; + public Optional MaxUses { get; set; } [JsonProperty("temporary")] - public bool Temporary { get; set; } = false; + public Optional Temporary { get; set; } [JsonProperty("xkcdpass")] - public bool XkcdPass { get; set; } = false; + public Optional XkcdPass { get; set; } } } diff --git a/src/Discord.Net/API/Rest/CreateGuildBanParams.cs b/src/Discord.Net/API/Rest/CreateGuildBanParams.cs index b8bf5b5cc..16e2c9846 100644 --- a/src/Discord.Net/API/Rest/CreateGuildBanParams.cs +++ b/src/Discord.Net/API/Rest/CreateGuildBanParams.cs @@ -5,6 +5,6 @@ namespace Discord.API.Rest public class CreateGuildBanParams { [JsonProperty("delete-message-days")] - public int PruneDays { get; set; } = 0; + public Optional PruneDays { get; set; } } } diff --git a/src/Discord.Net/API/Rest/CreateGuildChannelParams.cs b/src/Discord.Net/API/Rest/CreateGuildChannelParams.cs index 2badcf27c..11a1487ec 100644 --- a/src/Discord.Net/API/Rest/CreateGuildChannelParams.cs +++ b/src/Discord.Net/API/Rest/CreateGuildChannelParams.cs @@ -8,7 +8,8 @@ namespace Discord.API.Rest public string Name { get; set; } [JsonProperty("type")] public ChannelType Type { get; set; } + [JsonProperty("bitrate")] - public int Bitrate { get; set; } + public Optional Bitrate { get; set; } } } diff --git a/src/Discord.Net/API/Rest/CreateGuildParams.cs b/src/Discord.Net/API/Rest/CreateGuildParams.cs index d8563adbd..dd6e5b8fd 100644 --- a/src/Discord.Net/API/Rest/CreateGuildParams.cs +++ b/src/Discord.Net/API/Rest/CreateGuildParams.cs @@ -10,7 +10,8 @@ namespace Discord.API.Rest public string Name { get; set; } [JsonProperty("region")] public string Region { get; set; } + [JsonProperty("icon"), JsonConverter(typeof(ImageConverter))] - public Stream Icon { get; set; } + public Optional Icon { get; set; } } } diff --git a/src/Discord.Net/API/Rest/CreateMessageParams.cs b/src/Discord.Net/API/Rest/CreateMessageParams.cs index 6fdc8c2ad..457bbe841 100644 --- a/src/Discord.Net/API/Rest/CreateMessageParams.cs +++ b/src/Discord.Net/API/Rest/CreateMessageParams.cs @@ -6,9 +6,10 @@ namespace Discord.API.Rest { [JsonProperty("content")] public string Content { get; set; } = ""; + [JsonProperty("nonce", NullValueHandling = NullValueHandling.Ignore)] - public string Nonce { get; set; } = null; + public Optional Nonce { get; set; } [JsonProperty("tts", DefaultValueHandling = DefaultValueHandling.Ignore)] - public bool IsTTS { get; set; } = false; + public Optional IsTTS { get; set; } } } diff --git a/src/Discord.Net/API/Rest/GetChannelMessagesParams.cs b/src/Discord.Net/API/Rest/GetChannelMessagesParams.cs index ec6e5fe79..c14d1c65f 100644 --- a/src/Discord.Net/API/Rest/GetChannelMessagesParams.cs +++ b/src/Discord.Net/API/Rest/GetChannelMessagesParams.cs @@ -4,6 +4,7 @@ { public int Limit { get; set; } = DiscordConfig.MaxMessagesPerBatch; public Direction RelativeDirection { get; set; } = Direction.Before; - public ulong? RelativeMessageId { get; set; } = null; + + public Optional RelativeMessageId { get; set; } } } diff --git a/src/Discord.Net/API/Rest/GetGuildMembersParams.cs b/src/Discord.Net/API/Rest/GetGuildMembersParams.cs index 59931e934..bf4be2ee0 100644 --- a/src/Discord.Net/API/Rest/GetGuildMembersParams.cs +++ b/src/Discord.Net/API/Rest/GetGuildMembersParams.cs @@ -2,7 +2,7 @@ { public class GetGuildMembersParams { - public int? Limit { get; set; } = null; - public int Offset { get; set; } = 0; + public Optional Limit { get; set; } + public Optional Offset { get; set; } } } diff --git a/src/Discord.Net/API/Rest/ModifyChannelPermissionsParams.cs b/src/Discord.Net/API/Rest/ModifyChannelPermissionsParams.cs index d02df347d..f102cccca 100644 --- a/src/Discord.Net/API/Rest/ModifyChannelPermissionsParams.cs +++ b/src/Discord.Net/API/Rest/ModifyChannelPermissionsParams.cs @@ -5,8 +5,8 @@ namespace Discord.API.Rest public class ModifyChannelPermissionsParams { [JsonProperty("allow")] - public uint Allow { get; set; } + public Optional Allow { get; set; } [JsonProperty("deny")] - public uint Deny { get; set; } + public Optional Deny { get; set; } } } diff --git a/src/Discord.Net/API/Rest/ModifyCurrentUserParams.cs b/src/Discord.Net/API/Rest/ModifyCurrentUserParams.cs index f1512536f..64aab3181 100644 --- a/src/Discord.Net/API/Rest/ModifyCurrentUserParams.cs +++ b/src/Discord.Net/API/Rest/ModifyCurrentUserParams.cs @@ -7,14 +7,14 @@ namespace Discord.API.Rest public class ModifyCurrentUserParams { [JsonProperty("username")] - public string Username { get; set; } + public Optional Username { get; set; } [JsonProperty("email")] - public string Email { get; set; } + public Optional Email { get; set; } [JsonProperty("password")] - public string Password { get; set; } + public Optional Password { get; set; } [JsonProperty("new_password")] - public string NewPassword { get; set; } + public Optional NewPassword { get; set; } [JsonProperty("avatar"), JsonConverter(typeof(ImageConverter))] - public Stream Avatar { get; set; } + public Optional Avatar { get; set; } } } diff --git a/src/Discord.Net/API/Rest/ModifyGuildChannelParams.cs b/src/Discord.Net/API/Rest/ModifyGuildChannelParams.cs index b82f7b8d2..374f87377 100644 --- a/src/Discord.Net/API/Rest/ModifyGuildChannelParams.cs +++ b/src/Discord.Net/API/Rest/ModifyGuildChannelParams.cs @@ -5,8 +5,8 @@ namespace Discord.API.Rest public class ModifyGuildChannelParams { [JsonProperty("name")] - public string Name { get; set; } + public Optional Name { get; set; } [JsonProperty("position")] - public int Position { get; set; } + public Optional Position { get; set; } } } diff --git a/src/Discord.Net/API/Rest/ModifyGuildChannelsParams.cs b/src/Discord.Net/API/Rest/ModifyGuildChannelsParams.cs index 73ec46f7d..8444bb598 100644 --- a/src/Discord.Net/API/Rest/ModifyGuildChannelsParams.cs +++ b/src/Discord.Net/API/Rest/ModifyGuildChannelsParams.cs @@ -5,8 +5,8 @@ namespace Discord.API.Rest public class ModifyGuildChannelsParams { [JsonProperty("id")] - public ulong Id { get; set; } + public Optional Id { get; set; } [JsonProperty("position")] - public int Position { get; set; } + public Optional Position { get; set; } } } diff --git a/src/Discord.Net/API/Rest/ModifyGuildEmbedParams.cs b/src/Discord.Net/API/Rest/ModifyGuildEmbedParams.cs index 731497223..f717b4d52 100644 --- a/src/Discord.Net/API/Rest/ModifyGuildEmbedParams.cs +++ b/src/Discord.Net/API/Rest/ModifyGuildEmbedParams.cs @@ -6,8 +6,8 @@ namespace Discord.API.Rest public class ModifyGuildEmbedParams { [JsonProperty("enabled")] - public bool Enabled { get; set; } - [JsonProperty("channel"), JsonConverter(typeof(UInt64EntityConverter))] - public IVoiceChannel Channel { get; set; } + public Optional Enabled { get; set; } + [JsonProperty("channel")] + public Optional Channel { get; set; } } } diff --git a/src/Discord.Net/API/Rest/ModifyGuildIntegrationParams.cs b/src/Discord.Net/API/Rest/ModifyGuildIntegrationParams.cs index 9a5e9f81c..c58971c73 100644 --- a/src/Discord.Net/API/Rest/ModifyGuildIntegrationParams.cs +++ b/src/Discord.Net/API/Rest/ModifyGuildIntegrationParams.cs @@ -5,10 +5,10 @@ namespace Discord.API.Rest public class ModifyGuildIntegrationParams { [JsonProperty("expire_behavior")] - public int ExpireBehavior { get; set; } + public Optional ExpireBehavior { get; set; } [JsonProperty("expire_grace_period")] - public int ExpireGracePeriod { get; set; } + public Optional ExpireGracePeriod { get; set; } [JsonProperty("enable_emoticons")] - public bool EnableEmoticons { get; set; } + public Optional EnableEmoticons { get; set; } } } diff --git a/src/Discord.Net/API/Rest/ModifyGuildMemberParams.cs b/src/Discord.Net/API/Rest/ModifyGuildMemberParams.cs index 396e0314d..0fbaa6d15 100644 --- a/src/Discord.Net/API/Rest/ModifyGuildMemberParams.cs +++ b/src/Discord.Net/API/Rest/ModifyGuildMemberParams.cs @@ -1,17 +1,18 @@ -using Discord.Net.Converters; -using Newtonsoft.Json; +using Newtonsoft.Json; namespace Discord.API.Rest { public class ModifyGuildMemberParams { [JsonProperty("roles")] - public ulong[] Roles { get; set; } + public Optional Roles { get; set; } [JsonProperty("mute")] - public bool Mute { get; set; } + public Optional Mute { get; set; } [JsonProperty("deaf")] - public bool Deaf { get; set; } - [JsonProperty("channel_id"), JsonConverter(typeof(UInt64EntityConverter))] - public IVoiceChannel VoiceChannel { get; set; } + public Optional Deaf { get; set; } + [JsonProperty("nick")] + public Optional Nickname { get; set; } + [JsonProperty("channel_id")] + public Optional VoiceChannel { get; set; } } } diff --git a/src/Discord.Net/API/Rest/ModifyGuildParams.cs b/src/Discord.Net/API/Rest/ModifyGuildParams.cs index 1d5969ab2..e92b1f63c 100644 --- a/src/Discord.Net/API/Rest/ModifyGuildParams.cs +++ b/src/Discord.Net/API/Rest/ModifyGuildParams.cs @@ -7,20 +7,20 @@ namespace Discord.API.Rest public class ModifyGuildParams { [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("region"), JsonConverterAttribute(typeof(StringEntityConverter))] - public IVoiceRegion Region { get; set; } + public Optional Name { get; set; } + [JsonProperty("region")] + public Optional Region { get; set; } [JsonProperty("verification_level")] - public int VerificationLevel { get; set; } + public Optional VerificationLevel { get; set; } [JsonProperty("afk_channel_id")] - public ulong? AFKChannelId { get; set; } + public Optional AFKChannelId { get; set; } [JsonProperty("afk_timeout")] - public int AFKTimeout { get; set; } + public Optional AFKTimeout { get; set; } [JsonProperty("icon"), JsonConverter(typeof(ImageConverter))] - public Stream Icon { get; set; } + public Optional Icon { get; set; } [JsonProperty("owner_id")] - public GuildMember Owner { get; set; } + public Optional Owner { get; set; } [JsonProperty("splash"), JsonConverter(typeof(ImageConverter))] - public Stream Splash { get; set; } + public Optional Splash { get; set; } } } diff --git a/src/Discord.Net/API/Rest/ModifyGuildRoleParams.cs b/src/Discord.Net/API/Rest/ModifyGuildRoleParams.cs index 171d9cabe..58a715ae9 100644 --- a/src/Discord.Net/API/Rest/ModifyGuildRoleParams.cs +++ b/src/Discord.Net/API/Rest/ModifyGuildRoleParams.cs @@ -5,14 +5,14 @@ namespace Discord.API.Rest public class ModifyGuildRoleParams { [JsonProperty("name")] - public string Name { get; set; } + public Optional Name { get; set; } [JsonProperty("permissions")] - public uint Permissions { get; set; } + public Optional Permissions { get; set; } [JsonProperty("position")] - public int Position { get; set; } + public Optional Position { get; set; } [JsonProperty("color")] - public uint Color { get; set; } + public Optional Color { get; set; } [JsonProperty("hoist")] - public bool Hoist { get; set; } + public Optional Hoist { get; set; } } } diff --git a/src/Discord.Net/API/Rest/ModifyGuildRolesParams.cs b/src/Discord.Net/API/Rest/ModifyGuildRolesParams.cs index 7002079d5..286c2463d 100644 --- a/src/Discord.Net/API/Rest/ModifyGuildRolesParams.cs +++ b/src/Discord.Net/API/Rest/ModifyGuildRolesParams.cs @@ -5,6 +5,6 @@ namespace Discord.API.Rest public class ModifyGuildRolesParams : ModifyGuildRoleParams { [JsonProperty("id")] - public ulong Id { get; set; } + public Optional Id { get; set; } } } diff --git a/src/Discord.Net/API/Rest/ModifyMessageParams.cs b/src/Discord.Net/API/Rest/ModifyMessageParams.cs index d3b90b903..140bd93e3 100644 --- a/src/Discord.Net/API/Rest/ModifyMessageParams.cs +++ b/src/Discord.Net/API/Rest/ModifyMessageParams.cs @@ -5,6 +5,6 @@ namespace Discord.API.Rest public class ModifyMessageParams { [JsonProperty("content")] - public string Content { get; set; } = ""; + public Optional Content { get; set; } = ""; } } diff --git a/src/Discord.Net/API/Rest/ModifyTextChannelParams.cs b/src/Discord.Net/API/Rest/ModifyTextChannelParams.cs index cee07e7f1..28cfb3ee5 100644 --- a/src/Discord.Net/API/Rest/ModifyTextChannelParams.cs +++ b/src/Discord.Net/API/Rest/ModifyTextChannelParams.cs @@ -5,6 +5,6 @@ namespace Discord.API.Rest public class ModifyTextChannelParams : ModifyGuildChannelParams { [JsonProperty("topic")] - public string Topic { get; set; } + public Optional Topic { get; set; } } } diff --git a/src/Discord.Net/API/Rest/ModifyVoiceChannelParams.cs b/src/Discord.Net/API/Rest/ModifyVoiceChannelParams.cs index 1fbae95ac..b268783c8 100644 --- a/src/Discord.Net/API/Rest/ModifyVoiceChannelParams.cs +++ b/src/Discord.Net/API/Rest/ModifyVoiceChannelParams.cs @@ -5,6 +5,6 @@ namespace Discord.API.Rest public class ModifyVoiceChannelParams : ModifyGuildChannelParams { [JsonProperty("bitrate")] - public int Bitrate { get; set; } + public Optional Bitrate { get; set; } } } diff --git a/src/Discord.Net/API/Rest/UploadFileParams.cs b/src/Discord.Net/API/Rest/UploadFileParams.cs index 2e1248633..ad3c7bf3f 100644 --- a/src/Discord.Net/API/Rest/UploadFileParams.cs +++ b/src/Discord.Net/API/Rest/UploadFileParams.cs @@ -4,21 +4,22 @@ namespace Discord.API.Rest { public class UploadFileParams { - public string Content { get; set; } = ""; - public string Nonce { get; set; } = null; - public bool IsTTS { get; set; } = false; public string Filename { get; set; } = "unknown.dat"; + public Optional Content { get; set; } + public Optional Nonce { get; set; } + public Optional IsTTS { get; set; } + public IReadOnlyDictionary ToDictionary() { - var dic = new Dictionary - { - ["content"] = Content, - ["tts"] = IsTTS.ToString() - }; - if (Nonce != null) - dic.Add("nonce", Nonce); - return dic; + var d = new Dictionary(); + if (Content.IsSpecified) + d["content"] = Content.Value; + if (IsTTS.IsSpecified) + d["tts"] = IsTTS.Value.ToString(); + if (Nonce.IsSpecified) + d["nonce"] = Nonce.Value; + return d; } } } diff --git a/src/Discord.Net/Common/Entities/Guilds/IGuild.cs b/src/Discord.Net/Common/Entities/Guilds/IGuild.cs index 16db2c0e3..48b1a2dcd 100644 --- a/src/Discord.Net/Common/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net/Common/Entities/Guilds/IGuild.cs @@ -90,7 +90,9 @@ namespace Discord /// Gets a collection of all users in this guild. Task> GetUsers(); /// Gets the user in this guild with the provided id, or null if not found. - Task GetUser(ulong id); + Task GetUser(ulong id); + /// Gets the current user for this guild. + Task GetCurrentUser(); Task PruneUsers(int days = 30, bool simulate = false); } } \ No newline at end of file diff --git a/src/Discord.Net/Common/Entities/Users/IUser.cs b/src/Discord.Net/Common/Entities/Users/IUser.cs index 550c53e8b..4998d0419 100644 --- a/src/Discord.Net/Common/Entities/Users/IUser.cs +++ b/src/Discord.Net/Common/Entities/Users/IUser.cs @@ -12,6 +12,8 @@ namespace Discord ushort Discriminator { get; } /// Returns true if this user is a bot account. bool IsBot { get; } + /// Returns true is this user is the current logged-in account. + bool IsCurrentUser { get; } /// Gets the current status of this user. UserStatus Status { get; } /// Gets the username for this user. diff --git a/src/Discord.Net/Discord.Net.csproj b/src/Discord.Net/Discord.Net.csproj index f7c9e7a8c..a681e2931 100644 --- a/src/Discord.Net/Discord.Net.csproj +++ b/src/Discord.Net/Discord.Net.csproj @@ -66,6 +66,8 @@ + + @@ -95,6 +97,8 @@ + + diff --git a/src/Discord.Net/Net/Converters/ImageConverter.cs b/src/Discord.Net/Net/Converters/ImageConverter.cs index cf501050e..5fc25e8d2 100644 --- a/src/Discord.Net/Net/Converters/ImageConverter.cs +++ b/src/Discord.Net/Net/Converters/ImageConverter.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using Discord.API; +using Newtonsoft.Json; using System; using System.IO; @@ -6,7 +7,7 @@ namespace Discord.Net.Converters { public class ImageConverter : JsonConverter { - public override bool CanConvert(Type objectType) => objectType == typeof(Stream); + public override bool CanConvert(Type objectType) => objectType == typeof(Stream) || objectType == typeof(Optional); public override bool CanRead => true; public override bool CanWrite => true; @@ -17,6 +18,8 @@ namespace Discord.Net.Converters public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { + if (value is Optional) + value = (Optional)value; var stream = value as Stream; byte[] bytes = new byte[stream.Length - stream.Position]; diff --git a/src/Discord.Net/Net/Converters/OptionalContractResolver.cs b/src/Discord.Net/Net/Converters/OptionalContractResolver.cs new file mode 100644 index 000000000..cc0705671 --- /dev/null +++ b/src/Discord.Net/Net/Converters/OptionalContractResolver.cs @@ -0,0 +1,34 @@ +using Discord.API; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using System; +using System.Linq.Expressions; +using System.Reflection; + +namespace Discord.Net.Converters +{ + public class OptionalContractResolver : DefaultContractResolver + { + private static readonly PropertyInfo _isSpecified = typeof(IOptional).GetProperty(nameof(IOptional.IsSpecified)); + + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + var type = property.PropertyType; + + if (member.MemberType == MemberTypes.Property) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Optional<>)) + { + var parentArg = Expression.Parameter(typeof(object)); + var optional = Expression.Property(Expression.Convert(parentArg, property.DeclaringType), member as PropertyInfo); + var isSpecified = Expression.Property(optional, _isSpecified); + var lambda = Expression.Lambda>(isSpecified, parentArg).Compile(); + property.ShouldSerialize = x => lambda(x); + } + } + + return property; + } + } +} diff --git a/src/Discord.Net/Net/Converters/OptionalConverter.cs b/src/Discord.Net/Net/Converters/OptionalConverter.cs new file mode 100644 index 000000000..e75769e5f --- /dev/null +++ b/src/Discord.Net/Net/Converters/OptionalConverter.cs @@ -0,0 +1,23 @@ +using Discord.API; +using Newtonsoft.Json; +using System; + +namespace Discord.Net.Converters +{ + public class OptionalConverter : JsonConverter + { + public override bool CanConvert(Type objectType) => objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Optional<>); + public override bool CanRead => false; + public override bool CanWrite => true; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + throw new InvalidOperationException(); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + serializer.Serialize(writer, (value as IOptional).Value); + } + } +} diff --git a/src/Discord.Net/Net/Rest/DefaultRestClient.cs b/src/Discord.Net/Net/Rest/DefaultRestClient.cs index 0aec73597..356c67fb7 100644 --- a/src/Discord.Net/Net/Rest/DefaultRestClient.cs +++ b/src/Discord.Net/Net/Rest/DefaultRestClient.cs @@ -1,5 +1,4 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -33,6 +32,8 @@ namespace Discord.Net.Rest UseProxy = false, PreAuthenticate = false }); + + SetHeader("accept-encoding", "gzip, deflate"); } protected virtual void Dispose(bool disposing) { @@ -54,18 +55,18 @@ namespace Discord.Net.Rest _client.DefaultRequestHeaders.Add(key, value); } - public async Task Send(string method, string endpoint, string json = null) + public async Task Send(string method, string endpoint, string json = null, bool headerOnly = false) { string uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) { if (json != null) restRequest.Content = new StringContent(json, Encoding.UTF8, "application/json"); - return await SendInternal(restRequest, _cancelToken).ConfigureAwait(false); + return await SendInternal(restRequest, _cancelToken, headerOnly).ConfigureAwait(false); } } - public async Task Send(string method, string endpoint, IReadOnlyDictionary multipartParams) + public async Task Send(string method, string endpoint, IReadOnlyDictionary multipartParams, bool headerOnly = false) { string uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) @@ -95,11 +96,11 @@ namespace Discord.Net.Rest } } restRequest.Content = content; - return await SendInternal(restRequest, _cancelToken).ConfigureAwait(false); + return await SendInternal(restRequest, _cancelToken, headerOnly).ConfigureAwait(false); } } - private async Task SendInternal(HttpRequestMessage request, CancellationToken cancelToken) + private async Task SendInternal(HttpRequestMessage request, CancellationToken cancelToken, bool headerOnly) { int retryCount = 0; while (true) @@ -125,7 +126,10 @@ namespace Discord.Net.Rest throw new HttpException(response.StatusCode); } - return await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + if (headerOnly) + return null; + else + return await response.Content.ReadAsStreamAsync().ConfigureAwait(false); } } diff --git a/src/Discord.Net/Net/Rest/IRestClient.cs b/src/Discord.Net/Net/Rest/IRestClient.cs index c340c7c79..6ebc360b2 100644 --- a/src/Discord.Net/Net/Rest/IRestClient.cs +++ b/src/Discord.Net/Net/Rest/IRestClient.cs @@ -8,7 +8,7 @@ namespace Discord.Net.Rest { void SetHeader(string key, string value); - Task Send(string method, string endpoint, string json = null); - Task Send(string method, string endpoint, IReadOnlyDictionary multipartParams); + Task Send(string method, string endpoint, string json = null, bool headerOnly = false); + Task Send(string method, string endpoint, IReadOnlyDictionary multipartParams, bool headerOnly = false); } } diff --git a/src/Discord.Net/Net/Rest/RequestQueue/RequestQueueBucket.cs b/src/Discord.Net/Net/Rest/RequestQueue/RequestQueueBucket.cs index 4e8a3c520..2d14bc367 100644 --- a/src/Discord.Net/Net/Rest/RequestQueue/RequestQueueBucket.cs +++ b/src/Discord.Net/Net/Rest/RequestQueue/RequestQueueBucket.cs @@ -83,9 +83,9 @@ namespace Discord.Net.Rest { Stream stream; if (request.IsMultipart) - stream = await _parent.RestClient.Send(request.Method, request.Endpoint, request.MultipartParams).ConfigureAwait(false); + stream = await _parent.RestClient.Send(request.Method, request.Endpoint, request.MultipartParams, request.HeaderOnly).ConfigureAwait(false); else - stream = await _parent.RestClient.Send(request.Method, request.Endpoint, request.Json).ConfigureAwait(false); + stream = await _parent.RestClient.Send(request.Method, request.Endpoint, request.Json, request.HeaderOnly).ConfigureAwait(false); request.Promise.SetResult(stream); } catch (HttpRateLimitException ex) //Preemptive check failed, use Discord's time instead of our own diff --git a/src/Discord.Net/Net/Rest/RequestQueue/RestRequest.cs b/src/Discord.Net/Net/Rest/RequestQueue/RestRequest.cs index 86c7ca962..098dccc8a 100644 --- a/src/Discord.Net/Net/Rest/RequestQueue/RestRequest.cs +++ b/src/Discord.Net/Net/Rest/RequestQueue/RestRequest.cs @@ -9,29 +9,31 @@ namespace Discord.Net.Rest public string Method { get; } public string Endpoint { get; } public string Json { get; } + public bool HeaderOnly { get; } public IReadOnlyDictionary MultipartParams { get; } public TaskCompletionSource Promise { get; } public bool IsMultipart => MultipartParams != null; - public RestRequest(string method, string endpoint, string json) - : this(method, endpoint) + public RestRequest(string method, string endpoint, string json, bool headerOnly) + : this(method, endpoint, headerOnly) { Json = json; } - public RestRequest(string method, string endpoint, IReadOnlyDictionary multipartParams) - : this(method, endpoint) + public RestRequest(string method, string endpoint, IReadOnlyDictionary multipartParams, bool headerOnly) + : this(method, endpoint, headerOnly) { MultipartParams = multipartParams; } - private RestRequest(string method, string endpoint) + private RestRequest(string method, string endpoint, bool headerOnly) { Method = method; Endpoint = endpoint; Json = null; MultipartParams = null; + HeaderOnly = headerOnly; Promise = new TaskCompletionSource(); } } diff --git a/src/Discord.Net/Rest/Entities/Guilds/Guild.cs b/src/Discord.Net/Rest/Entities/Guilds/Guild.cs index 60170d0fa..ce530d0e2 100644 --- a/src/Discord.Net/Rest/Entities/Guilds/Guild.cs +++ b/src/Discord.Net/Rest/Entities/Guilds/Guild.cs @@ -306,7 +306,6 @@ namespace Discord.Rest var models = await Discord.BaseClient.GetGuildMembers(Id, args).ConfigureAwait(false); return models.Select(x => new GuildUser(this, x)); } - /// Gets the user in this guild with the provided id, or null if not found. public async Task GetUser(ulong id) { @@ -315,7 +314,11 @@ namespace Discord.Rest return new GuildUser(this, model); return null; } - + /// Gets a the current user. + public async Task GetCurrentUser() + { + return await GetUser(Discord.CurrentUser.Id).ConfigureAwait(false); + } public async Task PruneUsers(int days = 30, bool simulate = false) { var args = new GuildPruneParams() { Days = days }; @@ -367,6 +370,8 @@ namespace Discord.Rest => Task.FromResult>(Roles); async Task IGuild.GetUser(ulong id) => await GetUser(id).ConfigureAwait(false); + async Task IGuild.GetCurrentUser() + => await GetCurrentUser().ConfigureAwait(false); async Task> IGuild.GetUsers() => await GetUsers().ConfigureAwait(false); } diff --git a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs index b62656dfc..5ee975b21 100644 --- a/src/Discord.Net/Rest/Entities/Users/GuildUser.cs +++ b/src/Discord.Net/Rest/Entities/Users/GuildUser.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Threading.Tasks; using Model = Discord.API.GuildMember; @@ -82,8 +83,15 @@ namespace Discord.Rest var args = new ModifyGuildMemberParams(); func(args); - var model = await Discord.BaseClient.ModifyGuildMember(Guild.Id, Id, args).ConfigureAwait(false); - Update(model); + await Discord.BaseClient.ModifyGuildMember(Guild.Id, Id, args).ConfigureAwait(false); + if (args.Deaf.IsSpecified) + IsDeaf = args.Deaf; + if (args.Mute.IsSpecified) + IsMute = args.Mute; + if (args.Nickname.IsSpecified) + Nickname = args.Nickname; + if (args.Roles.IsSpecified) + _roles = args.Roles.Value.Select(x => Guild.GetRole(x)).Where(x => x != null).ToImmutableArray(); } diff --git a/src/Discord.Net/Rest/Entities/Users/User.cs b/src/Discord.Net/Rest/Entities/Users/User.cs index 9572c6620..70e199378 100644 --- a/src/Discord.Net/Rest/Entities/Users/User.cs +++ b/src/Discord.Net/Rest/Entities/Users/User.cs @@ -28,6 +28,8 @@ namespace Discord.Rest public string Mention => MentionHelper.Mention(this, false); /// public string NicknameMention => MentionHelper.Mention(this, true); + /// + public bool IsCurrentUser => Id == Discord.CurrentUser.Id; internal User(Model model) {