From 1d485eb6412d31924ccd885a2f21931ecba11d89 Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 25 Sep 2015 16:50:49 -0300 Subject: [PATCH] Refactored project structure and API calls. Exposed DiscordAPIClient as public. --- src/Discord.Net.Commands/CommandParser.cs | 1 + src/Discord.Net.Net45/Discord.Net.csproj | 123 +++-- src/Discord.Net/API/Common.cs | 295 +++++++++++ src/Discord.Net/{Net => }/API/Endpoints.cs | 3 +- .../{Net => }/API/HttpException.cs | 2 +- src/Discord.Net/API/Requests.cs | 142 +++++ .../{Net => }/API/RestClient.BuiltIn.cs | 0 .../{Net => }/API/RestClient.Events.cs | 2 +- .../{Net => }/API/RestClient.SharpRest.cs | 2 +- src/Discord.Net/{Net => }/API/RestClient.cs | 2 +- src/Discord.Net/DiscordClient.API.cs | 499 ++++++------------ src/Discord.Net/DiscordClient.Voice.cs | 3 +- src/Discord.Net/DiscordClient.cs | 63 +-- src/Discord.Net/Format.cs | 5 +- src/Discord.Net/Mention.cs | 7 +- src/Discord.Net/Models/Channel.cs | 9 +- src/Discord.Net/Models/Invite.cs | 11 +- src/Discord.Net/Models/Member.cs | 17 +- src/Discord.Net/Models/Message.cs | 2 +- src/Discord.Net/Models/PackedPermissions.cs | 3 +- src/Discord.Net/Models/Role.cs | 3 +- src/Discord.Net/Models/Server.cs | 9 +- src/Discord.Net/Models/User.cs | 11 +- src/Discord.Net/Net/API/Common.cs | 295 ----------- src/Discord.Net/Net/API/DiscordAPIClient.cs | 212 -------- src/Discord.Net/Net/API/Requests.cs | 158 ------ src/Discord.Net/Net/API/Responses.cs | 85 --- src/Discord.Net/Net/WebSockets/Commands.cs | 71 --- src/Discord.Net/Net/WebSockets/Events.cs | 118 ----- .../Net/WebSockets/VoiceCommands.cs | 62 --- src/Discord.Net/Net/WebSockets/VoiceEvents.cs | 41 -- src/Discord.Net/TimeoutException.cs | 4 - src/Discord.Net/WebSockets/Data/Commands.cs | 67 +++ .../Data}/DataWebSocket.cs | 23 +- .../Data}/DataWebSockets.Events.cs | 4 +- src/Discord.Net/WebSockets/Data/Events.cs | 115 ++++ src/Discord.Net/WebSockets/Voice/Commands.cs | 59 +++ src/Discord.Net/WebSockets/Voice/Events.cs | 38 ++ .../Voice}/VoiceWebSocket.Events.cs | 2 +- .../Voice}/VoiceWebSocket.cs | 16 +- .../{Net => }/WebSockets/WebSocket.BuiltIn.cs | 2 +- .../{Net => }/WebSockets/WebSocket.Events.cs | 2 +- .../{Net => }/WebSockets/WebSocket.cs | 6 +- .../{Net => }/WebSockets/WebSocketMessage.cs | 10 +- 44 files changed, 1061 insertions(+), 1543 deletions(-) create mode 100644 src/Discord.Net/API/Common.cs rename src/Discord.Net/{Net => }/API/Endpoints.cs (95%) rename src/Discord.Net/{Net => }/API/HttpException.cs (91%) create mode 100644 src/Discord.Net/API/Requests.cs rename src/Discord.Net/{Net => }/API/RestClient.BuiltIn.cs (100%) rename src/Discord.Net/{Net => }/API/RestClient.Events.cs (96%) rename src/Discord.Net/{Net => }/API/RestClient.SharpRest.cs (98%) rename src/Discord.Net/{Net => }/API/RestClient.cs (99%) delete mode 100644 src/Discord.Net/Net/API/Common.cs delete mode 100644 src/Discord.Net/Net/API/DiscordAPIClient.cs delete mode 100644 src/Discord.Net/Net/API/Requests.cs delete mode 100644 src/Discord.Net/Net/API/Responses.cs delete mode 100644 src/Discord.Net/Net/WebSockets/Commands.cs delete mode 100644 src/Discord.Net/Net/WebSockets/Events.cs delete mode 100644 src/Discord.Net/Net/WebSockets/VoiceCommands.cs delete mode 100644 src/Discord.Net/Net/WebSockets/VoiceEvents.cs create mode 100644 src/Discord.Net/WebSockets/Data/Commands.cs rename src/Discord.Net/{Net/WebSockets => WebSockets/Data}/DataWebSocket.cs (84%) rename src/Discord.Net/{Net/WebSockets => WebSockets/Data}/DataWebSockets.Events.cs (84%) create mode 100644 src/Discord.Net/WebSockets/Data/Events.cs create mode 100644 src/Discord.Net/WebSockets/Voice/Commands.cs create mode 100644 src/Discord.Net/WebSockets/Voice/Events.cs rename src/Discord.Net/{Net/WebSockets => WebSockets/Voice}/VoiceWebSocket.Events.cs (93%) rename src/Discord.Net/{Net/WebSockets => WebSockets/Voice}/VoiceWebSocket.cs (97%) rename src/Discord.Net/{Net => }/WebSockets/WebSocket.BuiltIn.cs (99%) rename src/Discord.Net/{Net => }/WebSockets/WebSocket.Events.cs (95%) rename src/Discord.Net/{Net => }/WebSockets/WebSocket.cs (98%) rename src/Discord.Net/{Net => }/WebSockets/WebSocketMessage.cs (71%) diff --git a/src/Discord.Net.Commands/CommandParser.cs b/src/Discord.Net.Commands/CommandParser.cs index 4742bbfb1..2073cb1cf 100644 --- a/src/Discord.Net.Commands/CommandParser.cs +++ b/src/Discord.Net.Commands/CommandParser.cs @@ -2,6 +2,7 @@ namespace Discord.Commands { + //TODO: Check support for escaping public static class CommandParser { private enum CommandParserPart diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index ffcc5fd0f..9b15aac0c 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -35,6 +35,17 @@ true true + + true + bin\FullDebug\ + TRACE;DEBUG;NET45,TEST_RESPONSES + true + 2 + full + AnyCPU + prompt + MinimumRecommendedRules.ruleset + ..\..\..\DiscordBot\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll @@ -57,6 +68,33 @@ + + API\Common.cs + + + API\Endpoints.cs + + + API\HttpException.cs + + + API\Requests.cs + + + API\Responses.cs + + + API\RestClient.BuiltIn.cs + + + API\RestClient.cs + + + API\RestClient.Events.cs + + + API\RestClient.SharpRest.cs + Audio\Opus.cs @@ -84,6 +122,9 @@ Collections\Users.cs + + DiscordAPIClient.cs + DiscordClient.API.cs @@ -150,74 +191,44 @@ Models\User.cs - - Net\API\Common.cs - - - Net\API\DiscordAPIClient.cs - - - Net\API\Endpoints.cs - - - Net\API\HttpException.cs - - - Net\API\Requests.cs - - - Net\API\Responses.cs - - - Net\API\RestClient.BuiltIn.cs - - - Net\API\RestClient.cs - - - Net\API\RestClient.Events.cs - - - Net\API\RestClient.SharpRest.cs - - - Net\WebSockets\Commands.cs + + TimeoutException.cs - - Net\WebSockets\DataWebSocket.cs + + WebSockets\Data\Commands.cs - - Net\WebSockets\DataWebSockets.Events.cs + + WebSockets\Data\DataWebSocket.cs - - Net\WebSockets\Events.cs + + WebSockets\Data\DataWebSockets.Events.cs - - Net\WebSockets\VoiceCommands.cs + + WebSockets\Data\Events.cs - - Net\WebSockets\VoiceEvents.cs + + WebSockets\Voice\Commands.cs - - Net\WebSockets\VoiceWebSocket.cs + + WebSockets\Voice\Events.cs - - Net\WebSockets\VoiceWebSocket.Events.cs + + WebSockets\Voice\VoiceWebSocket.cs - - Net\WebSockets\WebSocket.BuiltIn.cs + + WebSockets\Voice\VoiceWebSocket.Events.cs - - Net\WebSockets\WebSocket.cs + + WebSockets\WebSocket.BuiltIn.cs - - Net\WebSockets\WebSocket.Events.cs + + WebSockets\WebSocket.cs - - Net\WebSockets\WebSocketMessage.cs + + WebSockets\WebSocket.Events.cs - - TimeoutException.cs + + WebSockets\WebSocketMessage.cs diff --git a/src/Discord.Net/API/Common.cs b/src/Discord.Net/API/Common.cs new file mode 100644 index 000000000..79c6ab2ec --- /dev/null +++ b/src/Discord.Net/API/Common.cs @@ -0,0 +1,295 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; +using System; + +namespace Discord.API +{ + //User + public class UserReference + { + [JsonProperty("username")] + public string Username; + [JsonProperty("id")] + public string Id; + [JsonProperty("discriminator")] + public string Discriminator; + [JsonProperty("avatar")] + public string Avatar; + } + public class SelfUserInfo : UserReference + { + [JsonProperty("email")] + public string Email; + [JsonProperty("verified")] + public bool IsVerified; + } + + //Members + public class MemberReference + { + [JsonProperty("user_id")] + public string UserId; + [JsonProperty("user")] + public UserReference User; + [JsonProperty("guild_id")] + public string GuildId; + } + public class MemberInfo : MemberReference + { + [JsonProperty("joined_at")] + public DateTime? JoinedAt; + [JsonProperty("roles")] + public string[] Roles; + } + public class ExtendedMemberInfo : MemberInfo + { + [JsonProperty("mute")] + public bool IsMuted; + [JsonProperty("deaf")] + public bool IsDeafened; + } + public class PresenceMemberInfo : MemberReference + { + [JsonProperty("game_id")] + public string GameId; + [JsonProperty("status")] + public string Status; + } + public class VoiceMemberInfo : MemberReference + { + [JsonProperty("channel_id")] + public string ChannelId; + [JsonProperty("suppress")] + public bool? IsSuppressed; + [JsonProperty("session_id")] + public string SessionId; + [JsonProperty("self_mute")] + public bool? IsSelfMuted; + [JsonProperty("self_deaf")] + public bool? IsSelfDeafened; + [JsonProperty("mute")] + public bool IsMuted; + [JsonProperty("deaf")] + public bool IsDeafened; + [JsonProperty("token")] + public string Token; + } + + //Channels + public class ChannelReference + { + [JsonProperty("id")] + public string Id; + [JsonProperty("guild_id")] + public string GuildId; + [JsonProperty("name")] + public string Name; + [JsonProperty("type")] + public string Type; + } + public class ChannelInfo : ChannelReference + { + public sealed class PermissionOverwrite + { + [JsonProperty("type")] + public string Type; + [JsonProperty("id")] + public string Id; + [JsonProperty("deny")] + public uint Deny; + [JsonProperty("allow")] + public uint Allow; + } + + [JsonProperty("last_message_id")] + public string LastMessageId; + [JsonProperty("is_private")] + public bool IsPrivate; + [JsonProperty("position")] + public int Position; + [JsonProperty("permission_overwrites")] + public PermissionOverwrite[] PermissionOverwrites; + [JsonProperty("recipient")] + public UserReference Recipient; + } + + //Guilds (Servers) + public class GuildReference + { + [JsonProperty("id")] + public string Id; + [JsonProperty("name")] + public string Name; + } + public class GuildInfo : GuildReference + { + [JsonProperty("afk_channel_id")] + public string AFKChannelId; + [JsonProperty("afk_timeout")] + public int AFKTimeout; + [JsonProperty("embed_channel_id")] + public string EmbedChannelId; + [JsonProperty("embed_enabled")] + public bool EmbedEnabled; + [JsonProperty("icon")] + public string Icon; + [JsonProperty("joined_at")] + public DateTime? JoinedAt; + [JsonProperty("owner_id")] + public string OwnerId; + [JsonProperty("region")] + public string Region; + [JsonProperty("roles")] + public RoleInfo[] Roles; + } + public class ExtendedGuildInfo : GuildInfo + { + [JsonProperty("channels")] + public ChannelInfo[] Channels; + [JsonProperty("members")] + public ExtendedMemberInfo[] Members; + [JsonProperty("presences")] + public PresenceMemberInfo[] Presences; + [JsonProperty("voice_states")] + public VoiceMemberInfo[] VoiceStates; + } + + //Messages + public class MessageReference + { + [JsonProperty("id")] + public string Id; + [JsonProperty("channel_id")] + public string ChannelId; + [JsonProperty("message_id")] + public string MessageId { get { return Id; } set { Id = value; } } + } + public class Message : MessageReference + { + public sealed class Attachment + { + [JsonProperty("id")] + public string Id; + [JsonProperty("url")] + public string Url; + [JsonProperty("proxy_url")] + public string ProxyUrl; + [JsonProperty("size")] + public int Size; + [JsonProperty("filename")] + public string Filename; + [JsonProperty("width")] + public int Width; + [JsonProperty("height")] + public int Height; + } + public sealed class Embed + { + public sealed class Reference + { + [JsonProperty("url")] + public string Url; + [JsonProperty("name")] + public string Name; + } + public sealed class ThumbnailInfo + { + [JsonProperty("url")] + public string Url; + [JsonProperty("proxy_url")] + public string ProxyUrl; + [JsonProperty("width")] + public int Width; + [JsonProperty("height")] + public int Height; + } + + [JsonProperty("url")] + public string Url; + [JsonProperty("type")] + public string Type; + [JsonProperty("title")] + public string Title; + [JsonProperty("description")] + public string Description; + [JsonProperty("author")] + public Reference Author; + [JsonProperty("provider")] + public Reference Provider; + [JsonProperty("thumbnail")] + public ThumbnailInfo Thumbnail; + } + + [JsonProperty("tts")] + public bool IsTextToSpeech; + [JsonProperty("mention_everyone")] + public bool IsMentioningEveryone; + [JsonProperty("timestamp")] + public DateTime Timestamp; + [JsonProperty("edited_timestamp")] + public DateTime? EditedTimestamp; + [JsonProperty("mentions")] + public UserReference[] Mentions; + [JsonProperty("embeds")] + public Embed[] Embeds; //TODO: Parse this + [JsonProperty("attachments")] + public Attachment[] Attachments; + [JsonProperty("content")] + public string Content; + [JsonProperty("author")] + public UserReference Author; + [JsonProperty("nonce")] + public string Nonce; + } + + //Roles + public class RoleReference + { + [JsonProperty("guild_id")] + public string GuildId; + [JsonProperty("role_id")] + public string RoleId; + } + public class RoleInfo + { + [JsonProperty("permissions")] + public int Permissions; + [JsonProperty("name")] + public string Name; + [JsonProperty("id")] + public string Id; + } + + //Invites + public class Invite + { + [JsonProperty("inviter")] + public UserReference Inviter; + [JsonProperty("guild")] + public GuildReference Guild; + [JsonProperty("channel")] + public ChannelReference Channel; + [JsonProperty("code")] + public string Code; + [JsonProperty("xkcdpass")] + public string XkcdPass; + } + public class ExtendedInvite : Invite + { + [JsonProperty("max_age")] + public int MaxAge; + [JsonProperty("max_uses")] + public int MaxUses; + [JsonProperty("revoked")] + public bool IsRevoked; + [JsonProperty("temporary")] + public bool IsTemporary; + [JsonProperty("uses")] + public int Uses; + [JsonProperty("created_at")] + public DateTime CreatedAt; + } +} diff --git a/src/Discord.Net/Net/API/Endpoints.cs b/src/Discord.Net/API/Endpoints.cs similarity index 95% rename from src/Discord.Net/Net/API/Endpoints.cs rename to src/Discord.Net/API/Endpoints.cs index 048b75c98..4349781a7 100644 --- a/src/Discord.Net/Net/API/Endpoints.cs +++ b/src/Discord.Net/API/Endpoints.cs @@ -1,4 +1,4 @@ -namespace Discord.Net.API +namespace Discord.API { internal static class Endpoints { @@ -45,5 +45,6 @@ public const string StatusActiveMaintenance = "scheduled-maintenances/active.json"; public const string StatusUnresolvedMaintenance = "scheduled-maintenances/unresolved.json"; + public const string StatusUpcomingMaintenance = "scheduled-maintenances/upcoming.json"; } } diff --git a/src/Discord.Net/Net/API/HttpException.cs b/src/Discord.Net/API/HttpException.cs similarity index 91% rename from src/Discord.Net/Net/API/HttpException.cs rename to src/Discord.Net/API/HttpException.cs index 6530b109f..e0dba7d06 100644 --- a/src/Discord.Net/Net/API/HttpException.cs +++ b/src/Discord.Net/API/HttpException.cs @@ -1,7 +1,7 @@ using System; using System.Net; -namespace Discord.Net.API +namespace Discord.API { public class HttpException : Exception { diff --git a/src/Discord.Net/API/Requests.cs b/src/Discord.Net/API/Requests.cs new file mode 100644 index 000000000..1abea51d4 --- /dev/null +++ b/src/Discord.Net/API/Requests.cs @@ -0,0 +1,142 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; + +namespace Discord.API +{ + //Auth + internal sealed class RegisterRequest + { + [JsonProperty("fingerprint")] + public string Fingerprint; + [JsonProperty("username")] + public string Username; + } + internal sealed class LoginRequest + { + [JsonProperty("email")] + public string Email; + [JsonProperty("password")] + public string Password; + } + + //Channels + internal sealed class CreateChannelRequest + { + [JsonProperty("name")] + public string Name; + [JsonProperty("type")] + public string Type; + } + internal sealed class CreatePMChannelRequest + { + [JsonProperty("recipient_id")] + public string RecipientId; + } + internal sealed class EditChannelRequest + { + [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] + public string Name; + [JsonProperty("topic", NullValueHandling = NullValueHandling.Ignore)] + public string Topic; + } + + //Invites + internal sealed class CreateInviteRequest + { + [JsonProperty("max_age")] + public int MaxAge; + [JsonProperty("max_uses")] + public int MaxUses; + [JsonProperty("temporary")] + public bool IsTemporary; + [JsonProperty("xkcdpass")] + public bool WithXkcdPass; + } + + //Members + internal sealed class EditMemberRequest + { + [JsonProperty(PropertyName = "mute", NullValueHandling = NullValueHandling.Ignore)] + public bool? Mute; + [JsonProperty(PropertyName = "deaf", NullValueHandling = NullValueHandling.Ignore)] + public bool? Deaf; + [JsonProperty(PropertyName = "roles", NullValueHandling = NullValueHandling.Ignore)] + public string[] Roles; + } + + //Messages + internal sealed class SendMessageRequest + { + [JsonProperty("content")] + public string Content; + [JsonProperty("mentions")] + public string[] Mentions; + [JsonProperty("nonce", NullValueHandling = NullValueHandling.Ignore)] + public string Nonce; + [JsonProperty("tts", NullValueHandling = NullValueHandling.Ignore)] + public bool IsTTS; + } + internal sealed class EditMessageRequest + { + [JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)] + public string Content; + [JsonProperty("mentions", NullValueHandling = NullValueHandling.Ignore)] + public string[] Mentions; + } + + //Permissions + internal sealed class SetChannelPermissionsRequest //Both creates and modifies + { + [JsonProperty("id")] + public string Id; + [JsonProperty("type")] + public string Type; + [JsonProperty("allow")] + public uint Allow; + [JsonProperty("deny")] + public uint Deny; + } + + //Profile + internal sealed class EditProfileRequest + { + [JsonProperty(PropertyName = "password")] + public string CurrentPassword; + [JsonProperty(PropertyName = "email", NullValueHandling = NullValueHandling.Ignore)] + public string Email; + [JsonProperty(PropertyName = "new_password", NullValueHandling = NullValueHandling.Ignore)] + public string Password; + [JsonProperty(PropertyName = "username", NullValueHandling = NullValueHandling.Ignore)] + public string Username; + [JsonProperty(PropertyName = "avatar", NullValueHandling = NullValueHandling.Ignore)] + public string Avatar; + } + + //Roles + internal sealed class EditRoleRequest + { + [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] + public string Name; + [JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)] + public uint? Permissions; + } + + //Servers + internal sealed class CreateServerRequest + { + [JsonProperty("name")] + public string Name; + [JsonProperty("region")] + public string Region; + } + internal sealed class EditServerRequest + { + [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] + public string Name; + [JsonProperty("region", NullValueHandling = NullValueHandling.Ignore)] + public string Region; + } +} diff --git a/src/Discord.Net/Net/API/RestClient.BuiltIn.cs b/src/Discord.Net/API/RestClient.BuiltIn.cs similarity index 100% rename from src/Discord.Net/Net/API/RestClient.BuiltIn.cs rename to src/Discord.Net/API/RestClient.BuiltIn.cs diff --git a/src/Discord.Net/Net/API/RestClient.Events.cs b/src/Discord.Net/API/RestClient.Events.cs similarity index 96% rename from src/Discord.Net/Net/API/RestClient.Events.cs rename to src/Discord.Net/API/RestClient.Events.cs index 2f3fa3945..d5d257d49 100644 --- a/src/Discord.Net/Net/API/RestClient.Events.cs +++ b/src/Discord.Net/API/RestClient.Events.cs @@ -1,7 +1,7 @@ using System; using System.Net.Http; -namespace Discord.Net.API +namespace Discord.API { internal partial class RestClient { diff --git a/src/Discord.Net/Net/API/RestClient.SharpRest.cs b/src/Discord.Net/API/RestClient.SharpRest.cs similarity index 98% rename from src/Discord.Net/Net/API/RestClient.SharpRest.cs rename to src/Discord.Net/API/RestClient.SharpRest.cs index e47fa44bb..bbaea4a7a 100644 --- a/src/Discord.Net/Net/API/RestClient.SharpRest.cs +++ b/src/Discord.Net/API/RestClient.SharpRest.cs @@ -6,7 +6,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; -namespace Discord.Net.API +namespace Discord.API { internal class RestSharpRestEngine : IRestEngine { diff --git a/src/Discord.Net/Net/API/RestClient.cs b/src/Discord.Net/API/RestClient.cs similarity index 99% rename from src/Discord.Net/Net/API/RestClient.cs rename to src/Discord.Net/API/RestClient.cs index c2999dbc1..8c97e40b9 100644 --- a/src/Discord.Net/Net/API/RestClient.cs +++ b/src/Discord.Net/API/RestClient.cs @@ -6,7 +6,7 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; -namespace Discord.Net.API +namespace Discord.API { internal interface IRestEngine { diff --git a/src/Discord.Net/DiscordClient.API.cs b/src/Discord.Net/DiscordClient.API.cs index a949c38a2..b249b784c 100644 --- a/src/Discord.Net/DiscordClient.API.cs +++ b/src/Discord.Net/DiscordClient.API.cs @@ -1,6 +1,5 @@ -using Discord.Helpers; -using Discord.Net; -using Discord.Net.API; +using Discord.API; +using Discord.Helpers; using System; using System.Collections.Generic; using System.Linq; @@ -17,34 +16,54 @@ namespace Discord public partial class DiscordClient { - //Servers - /// Creates a new server with the provided name and region (see Regions). - public async Task CreateServer(string name, string region) + public const int MaxMessageSize = 2000; + + //Bans + /// Bans a user from the provided server. + public Task Ban(Member member) + => Ban(member?.ServerId, member?.UserId); + /// Bans a user from the provided server. + public Task Ban(Server server, User user) + => Ban(server?.Id, user?.Id); + /// Bans a user from the provided server. + public Task Ban(Server server, string userId) + => Ban(server?.Id, userId); + /// Bans a user from the provided server. + public Task Ban(string server, User user) + => Ban(server, user?.Id); + /// Bans a user from the provided server. + public Task Ban(string serverId, string userId) { CheckReady(); - if (name == null) throw new ArgumentNullException(nameof(name)); - if (region == null) throw new ArgumentNullException(nameof(region)); + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + if (userId == null) throw new ArgumentNullException(nameof(userId)); - var response = await _api.CreateServer(name, region).ConfigureAwait(false); - var server = _servers.GetOrAdd(response.Id); - server.Update(response); - return server; + return _api.Ban(serverId, userId); } - /// Leaves the provided server, destroying it if you are the owner. - public Task LeaveServer(Server server) - => LeaveServer(server?.Id); - /// Leaves the provided server, destroying it if you are the owner. - public async Task LeaveServer(string serverId) + /// Unbans a user from the provided server. + public Task Unban(Member member) + => Unban(member?.ServerId, member?.UserId); + /// Unbans a user from the provided server. + public Task Unban(Server server, User user) + => Unban(server?.Id, user?.Id); + /// Unbans a user from the provided server. + public Task Unban(Server server, string userId) + => Unban(server?.Id, userId); + /// Unbans a user from the provided server. + public Task Unban(string server, User user) + => Unban(server, user?.Id); + /// Unbans a user from the provided server. + public async Task Unban(string serverId, string userId) { CheckReady(); if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + if (userId == null) throw new ArgumentNullException(nameof(userId)); - try { await _api.LeaveServer(serverId).ConfigureAwait(false); } + try { await _api.Unban(serverId, userId).ConfigureAwait(false); } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - return _servers.TryRemove(serverId); } - + //Channels /// Creates a new channel with the provided name and type (see ChannelTypes). public Task CreateChannel(Server server, string name, string type) @@ -62,6 +81,7 @@ namespace Discord channel.Update(response); return channel; } + /// Returns the private channel with the provided user, creating one if it does not currently exist. public Task CreatePMChannel(string userId) => CreatePMChannel(_users[userId], userId); /// Returns the private channel with the provided user, creating one if it does not currently exist. @@ -70,7 +90,7 @@ namespace Discord public Task CreatePMChannel(Member member) => CreatePMChannel(member.User, member.UserId); private async Task CreatePMChannel(User user, string userId) { - CheckReady(); + CheckReady(); if (userId == null) throw new ArgumentNullException(nameof(userId)); Channel channel = null; @@ -85,6 +105,19 @@ namespace Discord return channel; } + /// Edits the provided channel, changing only non-null attributes. + public Task EditChannel(Channel channel) + => EditChannel(channel?.Id); + /// Edits the provided channel, changing only non-null attributes. + public Task EditChannel(string channelId, string name = null, string topic = null) + { + CheckReady(); + if (channelId == null) throw new ArgumentNullException(nameof(channelId)); + if (topic == null) throw new ArgumentNullException(nameof(topic)); + + return _api.EditChannel(channelId, name: name, topic: topic); + } + /// Destroys the provided channel. public Task DestroyChannel(Channel channel) => DestroyChannel(channel?.Id); @@ -99,52 +132,6 @@ namespace Discord return _channels.TryRemove(channelId); } - //Bans - /// Bans a user from the provided server. - public Task Ban(Member member) - => Ban(member?.ServerId, member?.UserId); - /// Bans a user from the provided server. - public Task Ban(Server server, User user) - => Ban(server?.Id, user?.Id); - /// Bans a user from the provided server. - public Task Ban(Server server, string userId) - => Ban(server?.Id, userId); - /// Bans a user from the provided server. - public Task Ban(string server, User user) - => Ban(server, user?.Id); - /// Bans a user from the provided server. - public Task Ban(string serverId, string userId) - { - CheckReady(); - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - if (userId == null) throw new ArgumentNullException(nameof(userId)); - - return _api.Ban(serverId, userId); - } - - /// Unbans a user from the provided server. - public Task Unban(Member member) - => Unban(member?.ServerId, member?.UserId); - /// Unbans a user from the provided server. - public Task Unban(Server server, User user) - => Unban(server?.Id, user?.Id); - /// Unbans a user from the provided server. - public Task Unban(Server server, string userId) - => Unban(server?.Id, userId); - /// Unbans a user from the provided server. - public Task Unban(string server, User user) - => Unban(server, user?.Id); - /// Unbans a user from the provided server. - public async Task Unban(string serverId, string userId) - { - CheckReady(); - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - if (userId == null) throw new ArgumentNullException(nameof(userId)); - - try { await _api.Unban(serverId, userId).ConfigureAwait(false); } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } - } - //Invites /// Creates a new invite to the default channel of the provided server. /// Time (in seconds) until the invite expires. Set to 0 to never expire. @@ -178,14 +165,29 @@ namespace Discord return invite; } + /// Deletes the provided invite. + public async Task DestroyInvite(string inviteId) + { + CheckReady(); + if (inviteId == null) throw new ArgumentNullException(nameof(inviteId)); + + try + { + //Check if this is a human-readable link and get its ID + var response = await _api.GetInvite(inviteId).ConfigureAwait(false); + await _api.DeleteInvite(response.Code).ConfigureAwait(false); + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + /// Gets more info about the provided invite code. /// Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode - public async Task GetInvite(string id) + public async Task GetInvite(string inviteIdOrXkcd) { CheckReady(); - if (id == null) throw new ArgumentNullException(nameof(id)); + if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd)); - var response = await _api.GetInvite(id).ConfigureAwait(false); + var response = await _api.GetInvite(inviteIdOrXkcd).ConfigureAwait(false); var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id); invite.Update(response); return invite; @@ -200,39 +202,42 @@ namespace Discord return _api.AcceptInvite(invite.Id); } /// Accepts the provided invite. - public async Task AcceptInvite(string code) + public async Task AcceptInvite(string inviteId) { CheckReady(); - if (code == null) throw new ArgumentNullException(nameof(code)); + if (inviteId == null) throw new ArgumentNullException(nameof(inviteId)); //Remove trailing slash and any non-code url parts - if (code.Length > 0 && code[code.Length - 1] == '/') - code = code.Substring(0, code.Length - 1); - int index = code.LastIndexOf('/'); + if (inviteId.Length > 0 && inviteId[inviteId.Length - 1] == '/') + inviteId = inviteId.Substring(0, inviteId.Length - 1); + int index = inviteId.LastIndexOf('/'); if (index >= 0) - code = code.Substring(index + 1); + inviteId = inviteId.Substring(index + 1); //Check if this is a human-readable link and get its ID - var invite = await GetInvite(code).ConfigureAwait(false); + var invite = await GetInvite(inviteId).ConfigureAwait(false); await _api.AcceptInvite(invite.Id).ConfigureAwait(false); } - /// Deletes the provided invite. - public async Task DeleteInvite(string code) + //Members + public Task EditMember(Member member, bool? mute = null, bool? deaf = null, string[] roles = null) + => EditMember(member?.ServerId, member?.UserId, mute, deaf, roles); + public Task EditMember(Server server, User user, bool? mute = null, bool? deaf = null, string[] roles = null) + => EditMember(server?.Id, user?.Id, mute, deaf, roles); + public Task EditMember(Server server, string userId, bool? mute = null, bool? deaf = null, string[] roles = null) + => EditMember(server?.Id, userId, mute, deaf, roles); + public Task EditMember(string serverId, User user, bool? mute = null, bool? deaf = null, string[] roles = null) + => EditMember(serverId, user?.Id, mute, deaf, roles); + public Task EditMember(string serverId, string userId, bool? mute = null, bool? deaf = null, string[] roles = null) { CheckReady(); - if (code == null) throw new ArgumentNullException(nameof(code)); + if (serverId == null) throw new NullReferenceException(nameof(serverId)); + if (userId == null) throw new NullReferenceException(nameof(userId)); - try - { - //Check if this is a human-readable link and get its ID - var response = await _api.GetInvite(code).ConfigureAwait(false); - await _api.DeleteInvite(response.Code).ConfigureAwait(false); - } - catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + return _api.EditMember(serverId, userId, mute, deaf, roles); } - //Chat + //Messages /// Sends a message to the provided channel. public Task SendMessage(Channel channel, string text) => SendMessage(channel?.Id, text, new string[0]); @@ -252,18 +257,18 @@ namespace Discord if (text == null) throw new ArgumentNullException(nameof(text)); if (mentions == null) throw new ArgumentNullException(nameof(mentions)); - int blockCount = (int)Math.Ceiling(text.Length / (double)DiscordAPIClient.MaxMessageSize); + int blockCount = (int)Math.Ceiling(text.Length / (double)MaxMessageSize); Message[] result = new Message[blockCount]; for (int i = 0; i < blockCount; i++) { - int index = i * DiscordAPIClient.MaxMessageSize; + int index = i * MaxMessageSize; string blockText = text.Substring(index, Math.Min(2000, text.Length - index)); var nonce = GenerateNonce(); if (_config.UseMessageQueue) { var msg = _messages.GetOrAdd("nonce_" + nonce, channel.Id, _currentUserId); var currentMember = _members[msg.UserId, channel.ServerId]; - msg.Update(new Net.API.Message + msg.Update(new API.Message { Content = blockText, Timestamp = DateTime.UtcNow, @@ -302,35 +307,37 @@ namespace Discord return await SendMessage(channel, text, new string[0]).ConfigureAwait(false); } - /// Edits a message the provided message. - public Task EditMessage(Message message, string text) - => EditMessage(message?.ChannelId, message?.Id, text, new string[0]); - /// Edits a message the provided message. - public Task EditMessage(Channel channel, string messageId, string text) - => EditMessage(channel?.Id, messageId, text, new string[0]); - /// Edits a message the provided message. - public Task EditMessage(string channelId, string messageId, string text) - => EditMessage(channelId, messageId, text, new string[0]); - /// Edits a message the provided message, mentioning certain users. + /// Sends a file to the provided channel. + public Task SendFile(Channel channel, string filePath) + => SendFile(channel?.Id, filePath); + /// Sends a file to the provided channel. + public Task SendFile(string channelId, string filePath) + { + CheckReady(); + if (channelId == null) throw new ArgumentNullException(nameof(channelId)); + if (filePath == null) throw new ArgumentNullException(nameof(filePath)); + + return _api.SendFile(channelId, filePath); + } + + /// Edits the provided message, changing only non-null attributes. /// While not required, it is recommended to include a mention reference in the text (see Mention.User). - public Task EditMessage(Message message, string text, string[] mentions) + public Task EditMessage(Message message, string text = null, string[] mentions = null) => EditMessage(message?.ChannelId, message?.Id, text, mentions); - /// Edits a message the provided message, mentioning certain users. + /// Edits the provided message, changing only non-null attributes. /// While not required, it is recommended to include a mention reference in the text (see Mention.User). - public Task EditMessage(Channel channel, string messageId, string text, string[] mentions) + public Task EditMessage(Channel channel, string messageId, string text = null, string[] mentions = null) => EditMessage(channel?.Id, messageId, text, mentions); - /// Edits a message the provided message, mentioning certain users. + /// Edits the provided message, changing only non-null attributes. /// While not required, it is recommended to include a mention reference in the text (see Mention.User). - public async Task EditMessage(string channelId, string messageId, string text, string[] mentions) + public async Task EditMessage(string channelId, string messageId, string text = null, string[] mentions = null) { CheckReady(); if (channelId == null) throw new ArgumentNullException(nameof(channelId)); if (messageId == null) throw new ArgumentNullException(nameof(messageId)); - if (text == null) throw new ArgumentNullException(nameof(text)); - if (mentions == null) throw new ArgumentNullException(nameof(mentions)); - if (text.Length > DiscordAPIClient.MaxMessageSize) - text = text.Substring(0, DiscordAPIClient.MaxMessageSize); + if (text != null && text.Length > MaxMessageSize) + text = text.Substring(0, MaxMessageSize); var model = await _api.EditMessage(messageId, channelId, text, mentions).ConfigureAwait(false); var msg = _messages[messageId]; @@ -383,19 +390,6 @@ namespace Discord catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } } } - - /// Sends a file to the provided channel. - public Task SendFile(Channel channel, string filePath) - => SendFile(channel?.Id, filePath); - /// Sends a file to the provided channel. - public Task SendFile(string channelId, string filePath) - { - CheckReady(); - if (channelId == null) throw new ArgumentNullException(nameof(channelId)); - if (filePath == null) throw new ArgumentNullException(nameof(filePath)); - - return _api.SendFile(channelId, filePath); - } /// Downloads last count messages from the server, starting at beforeMessageId if it's provided. public Task DownloadMessages(Channel channel, int count, string beforeMessageId = null, bool cache = true) @@ -449,113 +443,6 @@ namespace Discord return null; } - //Roles - /// Note: due to current API limitations, the created role cannot be returned. - public Task CreateRole(Server server) - => CreateRole(server?.Id); - /// Note: due to current API limitations, the created role cannot be returned. - public Task CreateRole(string serverId) - { - CheckReady(); - if (serverId == null) throw new NullReferenceException(nameof(serverId)); - - return _api.CreateRole(serverId); - } - - public Task RenameRole(Role role, string newName) - => RenameRole(role?.ServerId, role?.Id, newName); - public Task RenameRole(string serverId, string roleId, string newName) - { - CheckReady(); - if (roleId == null) throw new NullReferenceException(nameof(roleId)); - if (newName == null) throw new NullReferenceException(nameof(newName)); - - return _api.RenameRole(serverId, roleId, newName); - } - - public Task DeleteRole(Role role) - => DeleteRole(role?.ServerId, role?.Id); - public Task DeleteRole(string serverId, string roleId) - { - CheckReady(); - if (roleId == null) throw new NullReferenceException(nameof(roleId)); - - return _api.DeleteRole(serverId, roleId); - } - - public Task AddRoleMember(Role role, string serverId, string userId) - => AddRoleMember(role?.Id, GetMember(serverId, userId)); - public Task AddRoleMember(Role role, string serverId, User user) - => AddRoleMember(role?.Id, GetMember(serverId, user)); - public Task AddRoleMember(Role role, Server server, string userId) - => AddRoleMember(role?.Id, GetMember(server, userId)); - public Task AddRoleMember(Role role, Server server, User user) - => AddRoleMember(role?.Id, GetMember(server, user)); - public Task AddRoleMember(Role role, Member member) - => AddRoleMember(role?.Id, member); - public Task AddRoleMember(string roleId, string serverId, string userId) - => AddRoleMember(roleId, GetMember(serverId, userId)); - public Task AddRoleMember(string roleId, string serverId, User user) - => AddRoleMember(roleId, GetMember(serverId, user)); - public Task AddRoleMember(string roleId, Server server, string userId) - => AddRoleMember(roleId, GetMember(server, userId)); - public Task AddRoleMember(string roleId, Server server, User user) - => AddRoleMember(roleId, GetMember(server, user)); - public Task AddRoleMember(string roleId, Member member) - { - CheckReady(); - if (roleId == null) throw new NullReferenceException(nameof(roleId)); - if (member == null) throw new NullReferenceException(nameof(member)); - - if (!member.RoleIds.Contains(roleId)) - { - var oldRoles = member.RoleIds; - string[] newRoles = new string[oldRoles.Length + 1]; - for (int i = 0; i < oldRoles.Length; i++) - newRoles[i] = oldRoles[i]; - return _api.SetMemberRoles(member.ServerId, member.UserId, newRoles); - } - return TaskHelper.CompletedTask; - } - - public Task RemoveRoleMember(Role role, string serverId, string userId) - => RemoveRoleMember(role?.Id, GetMember(serverId, userId)); - public Task RemoveRoleMember(Role role, string serverId, User user) - => RemoveRoleMember(role?.Id, GetMember(serverId, user)); - public Task RemoveRoleMember(Role role, Server server, string userId) - => RemoveRoleMember(role?.Id, GetMember(server, userId)); - public Task RemoveRoleMember(Role role, Server server, User user) - => RemoveRoleMember(role?.Id, GetMember(server, user)); - public Task RemoveRoleMember(Role role, Member member) - => RemoveRoleMember(role?.Id, member); - public Task RemoveRoleMember(string roleId, string serverId, string userId) - => RemoveRoleMember(roleId, GetMember(serverId, userId)); - public Task RemoveRoleMember(string roleId, string serverId, User user) - => RemoveRoleMember(roleId, GetMember(serverId, user)); - public Task RemoveRoleMember(string roleId, Server server, string userId) - => RemoveRoleMember(roleId, GetMember(server, userId)); - public Task RemoveRoleMember(string roleId, Server server, User user) - => RemoveRoleMember(roleId, GetMember(server, user)); - public Task RemoveRoleMember(string roleId, Member member) - { - CheckReady(); - if (roleId == null) throw new NullReferenceException(nameof(roleId)); - if (member == null) throw new NullReferenceException(nameof(member)); - - if (member.RoleIds.Contains(roleId)) - { - var oldRoles = member.RoleIds; - string[] newRoles = new string[oldRoles.Length - 1]; - for (int i = 0, j = 0; i < oldRoles.Length; i++) - { - if (oldRoles[i] != roleId) - newRoles[j++] = oldRoles[i]; - } - return _api.SetMemberRoles(member.ServerId, member.UserId, newRoles); - } - return TaskHelper.CompletedTask; - } - //Permissions public Task SetChannelUserPermissions(Channel channel, Member member, PackedPermissions allow, PackedPermissions deny) => SetChannelPermissions(channel?.Id, member?.UserId, "member", allow, deny); @@ -585,7 +472,7 @@ namespace Discord if (channelId == null) throw new NullReferenceException(nameof(channelId)); if (userOrRoleId == null) throw new NullReferenceException(nameof(userOrRoleId)); - return _api.SetChannelPermissions(channelId, userOrRoleId, idType, allow, deny); + return _api.SetChannelPermissions(channelId, userOrRoleId, idType, allow.RawValue, deny.RawValue); //TODO: Remove permission from cache } @@ -625,128 +512,90 @@ namespace Discord catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } } - //Voice - /// Mutes a user on the provided server. - public Task Mute(Member member) - => Mute(member?.ServerId, member?.UserId); - /// Mutes a user on the provided server. - public Task Mute(Server server, User user) - => Mute(server?.Id, user?.Id); - /// Mutes a user on the provided server. - public Task Mute(Server server, string userId) - => Mute(server?.Id, userId); - /// Mutes a user on the provided server. - public Task Mute(string server, User user) - => Mute(server, user?.Id); - /// Mutes a user on the provided server. - public Task Mute(string serverId, string userId) + //Profile + public Task EditProfile(string currentPassword, + string username = null, string email = null, string password = null, + AvatarImageType avatarType = AvatarImageType.Png, byte[] avatar = null) { - CheckReady(); - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - if (userId == null) throw new ArgumentNullException(nameof(userId)); + if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); - return _api.Mute(serverId, userId); + return _api.EditProfile(currentPassword, username: username, email: email, password: password, + avatarType: avatarType, avatar: avatar); } - /// Mutes a user on the provided server. - public Task Unmute(Member member) - => Unmute(member?.ServerId, member?.UserId); - /// Unmutes a user on the provided server. - public Task Unmute(Server server, User user) - => Unmute(server?.Id, user?.Id); - /// Unmutes a user on the provided server. - public Task Unmute(Server server, string userId) - => Unmute(server?.Id, userId); - /// Unmutes a user on the provided server. - public Task Unmute(string server, User user) - => Unmute(server, user?.Id); - /// Unmutes a user on the provided server. - public Task Unmute(string serverId, string userId) + //Roles + /// Note: due to current API limitations, the created role cannot be returned. + public Task CreateRole(Server server) + => CreateRole(server?.Id); + /// Note: due to current API limitations, the created role cannot be returned. + public Task CreateRole(string serverId) { CheckReady(); - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - if (userId == null) throw new ArgumentNullException(nameof(userId)); + if (serverId == null) throw new NullReferenceException(nameof(serverId)); - return _api.Unmute(serverId, userId); + return _api.CreateRole(serverId); } - /// Deafens a user on the provided server. - public Task Deafen(Member member) - => Deafen(member?.ServerId, member?.UserId); - /// Deafens a user on the provided server. - public Task Deafen(Server server, User user) - => Deafen(server?.Id, user?.Id); - /// Deafens a user on the provided server. - public Task Deafen(Server server, string userId) - => Deafen(server?.Id, userId); - /// Deafens a user on the provided server. - public Task Deafen(string server, User user) - => Deafen(server, user?.Id); - /// Deafens a user on the provided server. - public Task Deafen(string serverId, string userId) + public Task EditRole(Role role, string newName) + => EditRole(role?.ServerId, role?.Id, newName); + public Task EditRole(string serverId, string roleId, string name = null, PackedPermissions permissions = null) { CheckReady(); - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - if (userId == null) throw new ArgumentNullException(nameof(userId)); + if (serverId == null) throw new NullReferenceException(nameof(serverId)); + if (roleId == null) throw new NullReferenceException(nameof(roleId)); - return _api.Deafen(serverId, userId); + return _api.EditRole(serverId, roleId, name: name, permissions: permissions?.RawValue); } - /// Undeafens a user on the provided server. - public Task Undeafen(Member member) - => Undeafen(member?.ServerId, member?.UserId); - /// Undeafens a user on the provided server. - public Task Undeafen(Server server, User user) - => Undeafen(server?.Id, user?.Id); - /// Undeafens a user on the provided server. - public Task Undeafen(Server server, string userId) - => Undeafen(server?.Id, userId); - /// Undeafens a user on the provided server. - public Task Undeafen(string server, User user) - => Undeafen(server, user?.Id); - /// Undeafens a user on the provided server. - public Task Undeafen(string serverId, string userId) + public Task DeleteRole(Role role) + => DeleteRole(role?.ServerId, role?.Id); + public Task DeleteRole(string serverId, string roleId) { CheckReady(); - if (serverId == null) throw new ArgumentNullException(nameof(serverId)); - if (userId == null) throw new ArgumentNullException(nameof(userId)); + if (serverId == null) throw new NullReferenceException(nameof(serverId)); + if (roleId == null) throw new NullReferenceException(nameof(roleId)); - return _api.Undeafen(serverId, userId); + return _api.DeleteRole(serverId, roleId); } - //Profile - /// Changes your username to newName. - public async Task ChangeUsername(string newName, string currentEmail, string currentPassword) - { - CheckReady(); - var response = await _api.ChangeUsername(newName, currentEmail, currentPassword).ConfigureAwait(false); - _currentUser.Update(response); - foreach (var membership in _currentUser.Memberships) - membership.Update(response); - } - /// Changes your email to newEmail. - public async Task ChangeEmail(string newEmail, string currentPassword) + //Servers + /// Creates a new server with the provided name and region (see Regions). + public async Task CreateServer(string name, string region) { CheckReady(); - var response = await _api.ChangeEmail(newEmail, currentPassword).ConfigureAwait(false); - _currentUser.Update(response); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (region == null) throw new ArgumentNullException(nameof(region)); + + var response = await _api.CreateServer(name, region).ConfigureAwait(false); + var server = _servers.GetOrAdd(response.Id); + server.Update(response); + return server; } - /// Changes your password to newPassword. - public async Task ChangePassword(string newPassword, string currentEmail, string currentPassword) + + /// Edits the provided server, changing only non-null attributes. + public Task EditServer(Server server) + => EditServer(server?.Id); + /// Edits the provided server, changing only non-null attributes. + public Task EditServer(string serverId, string name = null, string region = null) { CheckReady(); - await _api.ChangePassword(newPassword, currentEmail, currentPassword).ConfigureAwait(false); + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + + return _api.EditServer(serverId, name: name, region: region); } - /// Changes your avatar. - /// Only supports PNG and JPEG (see AvatarImageType) - public async Task ChangeAvatar(AvatarImageType imageType, byte[] bytes, string currentEmail, string currentPassword) + /// Leaves the provided server, destroying it if you are the owner. + public Task LeaveServer(Server server) + => LeaveServer(server?.Id); + /// Leaves the provided server, destroying it if you are the owner. + public async Task LeaveServer(string serverId) { CheckReady(); - var response = await _api.ChangeAvatar(imageType, bytes, currentEmail, currentPassword).ConfigureAwait(false); - _currentUser.Update(response); - foreach (var membership in _currentUser.Memberships) - membership.Update(response); + if (serverId == null) throw new ArgumentNullException(nameof(serverId)); + + try { await _api.LeaveServer(serverId).ConfigureAwait(false); } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + return _servers.TryRemove(serverId); } } } diff --git a/src/Discord.Net/DiscordClient.Voice.cs b/src/Discord.Net/DiscordClient.Voice.cs index 3a434a99e..fd32c36f1 100644 --- a/src/Discord.Net/DiscordClient.Voice.cs +++ b/src/Discord.Net/DiscordClient.Voice.cs @@ -1,4 +1,5 @@ using Discord.Helpers; +using Discord.WebSockets; using System; using System.Threading.Tasks; @@ -36,7 +37,7 @@ namespace Discord { CheckReady(checkVoice: true); - if (_voiceSocket.State != Net.WebSockets.WebSocketState.Disconnected) + if (_voiceSocket.State != WebSocketState.Disconnected) { var serverId = _voiceSocket.CurrentVoiceServerId; if (serverId != null) diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 90f5a4f64..08225bc53 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -1,8 +1,8 @@ -using Discord.Collections; +using Discord.API; +using Discord.Collections; using Discord.Helpers; -using Discord.Net; -using Discord.Net.API; -using Discord.Net.WebSockets; +using Discord.WebSockets; +using Discord.WebSockets.Data; using Newtonsoft.Json; using System; using System.Collections.Concurrent; @@ -10,6 +10,7 @@ using System.Net; using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; +using VoiceWebSocket = Discord.WebSockets.Voice.VoiceWebSocket; namespace Discord { @@ -279,7 +280,7 @@ namespace Discord //Global case "READY": //Resync { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); _currentUserId = data.User.Id; _currentUser = _users.GetOrAdd(data.User.Id); _currentUser.Update(data.User); @@ -303,7 +304,7 @@ namespace Discord //Servers case "GUILD_CREATE": { - var model = e.Payload.ToObject(_serializer); + var model = e.Payload.ToObject(_serializer); var server = _servers.GetOrAdd(model.Id); server.Update(model); RaiseServerCreated(server); @@ -311,7 +312,7 @@ namespace Discord break; case "GUILD_UPDATE": { - var model = e.Payload.ToObject(_serializer); + var model = e.Payload.ToObject(_serializer); var server = _servers[model.Id]; if (server != null) { @@ -322,7 +323,7 @@ namespace Discord break; case "GUILD_DELETE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var server = _servers.TryRemove(data.Id); if (server != null) RaiseServerDestroyed(server); @@ -332,7 +333,7 @@ namespace Discord //Channels case "CHANNEL_CREATE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); Channel channel; if (data.IsPrivate) { @@ -348,7 +349,7 @@ namespace Discord break; case "CHANNEL_UPDATE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var channel = _channels[data.Id]; if (channel != null) { @@ -359,7 +360,7 @@ namespace Discord break; case "CHANNEL_DELETE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var channel = _channels.TryRemove(data.Id); if (channel != null) RaiseChannelDestroyed(channel); @@ -369,7 +370,7 @@ namespace Discord //Members case "GUILD_MEMBER_ADD": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var user = _users.GetOrAdd(data.User.Id); var member = _members.GetOrAdd(data.User.Id, data.GuildId); user.Update(data.User); @@ -381,7 +382,7 @@ namespace Discord break; case "GUILD_MEMBER_UPDATE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var member = _members[data.User.Id, data.GuildId]; if (member != null) { @@ -392,7 +393,7 @@ namespace Discord break; case "GUILD_MEMBER_REMOVE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var member = _members.TryRemove(data.UserId, data.GuildId); if (member != null) RaiseUserRemoved(member); @@ -402,7 +403,7 @@ namespace Discord //Roles case "GUILD_ROLE_CREATE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var role = _roles.GetOrAdd(data.Data.Id, data.GuildId); role.Update(data.Data); RaiseRoleUpdated(role); @@ -410,7 +411,7 @@ namespace Discord break; case "GUILD_ROLE_UPDATE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var role = _roles[data.Data.Id]; if (role != null) role.Update(data.Data); @@ -419,7 +420,7 @@ namespace Discord break; case "GUILD_ROLE_DELETE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var role = _roles.TryRemove(data.RoleId); if (role != null) RaiseRoleDeleted(role); @@ -429,7 +430,7 @@ namespace Discord //Bans case "GUILD_BAN_ADD": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var server = _servers[data.GuildId]; if (server != null) { @@ -440,7 +441,7 @@ namespace Discord break; case "GUILD_BAN_REMOVE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var server = _servers[data.GuildId]; if (server != null && server.RemoveBan(data.UserId)) RaiseBanRemoved(data.UserId, server); @@ -450,7 +451,7 @@ namespace Discord //Messages case "MESSAGE_CREATE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); Message msg = null; bool wasLocal = _config.UseMessageQueue && data.Author.Id == _currentUserId && data.Nonce != null; @@ -490,7 +491,7 @@ namespace Discord break; case "MESSAGE_UPDATE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var msg = _messages[data.Id]; if (msg != null) { @@ -501,7 +502,7 @@ namespace Discord break; case "MESSAGE_DELETE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var msg = _messages.TryRemove(data.Id); if (msg != null) RaiseMessageDeleted(msg); @@ -509,7 +510,7 @@ namespace Discord break; case "MESSAGE_ACK": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var msg = GetMessage(data.MessageId); if (msg != null) RaiseMessageReadRemotely(msg); @@ -519,7 +520,7 @@ namespace Discord //Statuses case "PRESENCE_UPDATE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var member = _members[data.User.Id, data.GuildId]; /*if (_config.TrackActivity) { @@ -536,7 +537,7 @@ namespace Discord break; case "VOICE_STATE_UPDATE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var member = _members[data.UserId, data.GuildId]; /*if (_config.TrackActivity) { @@ -558,7 +559,7 @@ namespace Discord break; case "TYPING_START": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var channel = _channels[data.ChannelId]; var user = _users[data.UserId]; @@ -587,7 +588,7 @@ namespace Discord //Voice case "VOICE_SERVER_UPDATE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); if (data.GuildId == _voiceSocket.CurrentVoiceServerId) { var server = _servers[data.GuildId]; @@ -603,7 +604,7 @@ namespace Discord //Settings case "USER_UPDATE": { - var data = e.Payload.ToObject(_serializer); + var data = e.Payload.ToObject(_serializer); var user = _users[data.Id]; if (user != null) { @@ -670,8 +671,8 @@ namespace Discord _token = token; _state = (int)DiscordClientState.Connecting; - string url = (await _api.GetWebSocketEndpoint().ConfigureAwait(false)).Url; - if (_config.LogLevel >= LogMessageSeverity.Verbose) + string url = (await _api.Gateway().ConfigureAwait(false)).Url; + if (_config.LogLevel >= LogMessageSeverity.Verbose) RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Websocket endpoint: {url}"); _dataSocket.Host = url; @@ -838,7 +839,7 @@ namespace Discord while (_pendingMessages.TryDequeue(out msg)) { bool hasFailed = false; - Responses.SendMessage response = null; + SendMessageResponse response = null; try { response = await _api.SendMessage(msg.ChannelId, msg.RawText, msg.MentionIds, msg.Nonce, msg.IsTTS).ConfigureAwait(false); diff --git a/src/Discord.Net/Format.cs b/src/Discord.Net/Format.cs index 32995ed7f..faf0fdcf1 100644 --- a/src/Discord.Net/Format.cs +++ b/src/Discord.Net/Format.cs @@ -1,5 +1,4 @@ -using Discord.Net.API; -using System.Text; +using System.Text; namespace Discord { @@ -11,7 +10,7 @@ namespace Discord static Format() { _patterns = new string[] { "__", "_", "**", "*", "~~" }; - _builder = new StringBuilder(DiscordAPIClient.MaxMessageSize); + _builder = new StringBuilder(DiscordClient.MaxMessageSize); } /// Removes all special formatting characters from the provided text. diff --git a/src/Discord.Net/Mention.cs b/src/Discord.Net/Mention.cs index f80284b6c..07a20dfa1 100644 --- a/src/Discord.Net/Mention.cs +++ b/src/Discord.Net/Mention.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Discord +namespace Discord { public static class Mention { diff --git a/src/Discord.Net/Models/Channel.cs b/src/Discord.Net/Models/Channel.cs index 731dad8e3..0c8ac6e7e 100644 --- a/src/Discord.Net/Models/Channel.cs +++ b/src/Discord.Net/Models/Channel.cs @@ -1,5 +1,4 @@ -using Discord.Net.API; -using Newtonsoft.Json; +using Newtonsoft.Json; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -64,14 +63,14 @@ namespace Discord _messages = new ConcurrentDictionary(); } - internal void Update(ChannelReference model) + internal void Update(API.ChannelReference model) { Name = model.Name; Type = model.Type; } - internal void Update(ChannelInfo model) + internal void Update(API.ChannelInfo model) { - Update(model as ChannelReference); + Update(model as API.ChannelReference); Position = model.Position; diff --git a/src/Discord.Net/Models/Invite.cs b/src/Discord.Net/Models/Invite.cs index e8916a848..27c85d93a 100644 --- a/src/Discord.Net/Models/Invite.cs +++ b/src/Discord.Net/Models/Invite.cs @@ -1,5 +1,4 @@ -using Discord.Net.API; -using Newtonsoft.Json; +using Newtonsoft.Json; namespace Discord { @@ -24,7 +23,7 @@ namespace Discord public string XkcdPass { get; } /// Returns a URL for this invite using XkcdPass if available or Id if not. - public string Url => Endpoints.InviteUrl(XkcdPass ?? Id); + public string Url => API.Endpoints.InviteUrl(XkcdPass ?? Id); /// Returns the id of the user that created this invite. public string InviterId { get; internal set; } @@ -54,16 +53,16 @@ namespace Discord public override string ToString() => XkcdPass ?? Id; - internal void Update(Net.API.Invite model) + internal void Update(API.Invite model) { ChannelId = model.Channel.Id; InviterId = model.Inviter?.Id; ServerId = model.Guild.Id; } - internal void Update(Net.API.ExtendedInvite model) + internal void Update(API.ExtendedInvite model) { - Update(model as Net.API.Invite); + Update(model as API.Invite); IsRevoked = model.IsRevoked; IsTemporary = model.IsTemporary; MaxAge = model.MaxAge; diff --git a/src/Discord.Net/Models/Member.cs b/src/Discord.Net/Models/Member.cs index a181bb293..812565d39 100644 --- a/src/Discord.Net/Models/Member.cs +++ b/src/Discord.Net/Models/Member.cs @@ -1,5 +1,4 @@ -using Discord.Net.API; -using Newtonsoft.Json; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; @@ -17,7 +16,7 @@ namespace Discord /// Returns the unique identifier for this user's current avatar. public string AvatarId { get; internal set; } /// Returns the URL to this user's current avatar. - public string AvatarUrl => Endpoints.UserAvatar(UserId, AvatarId); + public string AvatarUrl => API.Endpoints.UserAvatar(UserId, AvatarId); /// Returns the datetime that this user joined this server. public DateTime JoinedAt { get; internal set; } @@ -70,7 +69,7 @@ namespace Discord public override string ToString() => UserId; - internal void Update(UserReference model) + internal void Update(API.UserReference model) { if (model.Avatar != null) AvatarId = model.Avatar; @@ -79,7 +78,7 @@ namespace Discord if (model.Username != null) Name = model.Username; } - internal void Update(MemberInfo model) + internal void Update(API.MemberInfo model) { if (model.User != null) Update(model.User); @@ -87,13 +86,13 @@ namespace Discord if (model.JoinedAt.HasValue) JoinedAt = model.JoinedAt.Value; } - internal void Update(ExtendedMemberInfo model) + internal void Update(API.ExtendedMemberInfo model) { - Update(model as MemberInfo); + Update(model as API.MemberInfo); IsDeafened = model.IsDeafened; IsMuted = model.IsMuted; } - internal void Update(PresenceMemberInfo model) + internal void Update(API.PresenceMemberInfo model) { if (Status != model.Status) { @@ -103,7 +102,7 @@ namespace Discord } GameId = model.GameId; } - internal void Update(VoiceMemberInfo model) + internal void Update(API.VoiceMemberInfo model) { IsDeafened = model.IsDeafened; IsMuted = model.IsMuted; diff --git a/src/Discord.Net/Models/Message.cs b/src/Discord.Net/Models/Message.cs index d597e7f9f..e76b6c497 100644 --- a/src/Discord.Net/Models/Message.cs +++ b/src/Discord.Net/Models/Message.cs @@ -122,7 +122,7 @@ namespace Discord UserId = userId; } - internal void Update(Net.API.Message model) + internal void Update(API.Message model) { if (model.Attachments != null) { diff --git a/src/Discord.Net/Models/PackedPermissions.cs b/src/Discord.Net/Models/PackedPermissions.cs index 7db3d9374..c48114ddc 100644 --- a/src/Discord.Net/Models/PackedPermissions.cs +++ b/src/Discord.Net/Models/PackedPermissions.cs @@ -71,8 +71,7 @@ namespace Discord else _rawValue &= ~(1U << (pos - 1)); } - - public static implicit operator uint (PackedPermissions perms) => perms._rawValue; + public PackedPermissions Copy() => new PackedPermissions(false, _rawValue); } } diff --git a/src/Discord.Net/Models/Role.cs b/src/Discord.Net/Models/Role.cs index 4eb1a1f25..fd19f3853 100644 --- a/src/Discord.Net/Models/Role.cs +++ b/src/Discord.Net/Models/Role.cs @@ -1,5 +1,4 @@ using Newtonsoft.Json; -using System.Threading; namespace Discord { @@ -29,7 +28,7 @@ namespace Discord Permissions = new PackedPermissions(true); } - internal void Update(Net.API.RoleInfo model) + internal void Update(API.RoleInfo model) { Name = model.Name; Permissions.RawValue = (uint)model.Permissions; diff --git a/src/Discord.Net/Models/Server.cs b/src/Discord.Net/Models/Server.cs index 971b703b2..2c5e35b55 100644 --- a/src/Discord.Net/Models/Server.cs +++ b/src/Discord.Net/Models/Server.cs @@ -1,5 +1,4 @@ -using Discord.Net.API; -using Newtonsoft.Json; +using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -100,7 +99,7 @@ namespace Discord _roles = new ConcurrentDictionary(); } - internal void Update(GuildInfo model) + internal void Update(API.GuildInfo model) { AFKChannelId = model.AFKChannelId; AFKTimeout = model.AFKTimeout; @@ -117,9 +116,9 @@ namespace Discord role.Update(subModel); } } - internal void Update(ExtendedGuildInfo model) + internal void Update(API.ExtendedGuildInfo model) { - Update(model as GuildInfo); + Update(model as API.GuildInfo); var channels = _client.Channels; foreach (var subModel in model.Channels) diff --git a/src/Discord.Net/Models/User.cs b/src/Discord.Net/Models/User.cs index 857ea08b0..348a7ce30 100644 --- a/src/Discord.Net/Models/User.cs +++ b/src/Discord.Net/Models/User.cs @@ -1,5 +1,4 @@ -using Discord.Net.API; -using Newtonsoft.Json; +using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -24,7 +23,7 @@ namespace Discord /// Returns the unique identifier for this user's current avatar. public string AvatarId { get; internal set; } /// Returns the URL to this user's current avatar. - public string AvatarUrl => Endpoints.UserAvatar(Id, AvatarId); + public string AvatarUrl => API.Endpoints.UserAvatar(Id, AvatarId); /// Returns the email for this user. /// This field is only ever populated for the current logged in user. @@ -78,7 +77,7 @@ namespace Discord _servers = new ConcurrentDictionary(); } - internal void Update(UserReference model) + internal void Update(API.UserReference model) { if (model.Avatar != null) AvatarId = model.Avatar; @@ -87,9 +86,9 @@ namespace Discord if (model.Username != null) Name = model.Username; } - internal void Update(SelfUserInfo model) + internal void Update(API.SelfUserInfo model) { - Update(model as UserReference); + Update(model as API.UserReference); Email = model.Email; IsVerified = model.IsVerified; } diff --git a/src/Discord.Net/Net/API/Common.cs b/src/Discord.Net/Net/API/Common.cs deleted file mode 100644 index 230e286dd..000000000 --- a/src/Discord.Net/Net/API/Common.cs +++ /dev/null @@ -1,295 +0,0 @@ -//Ignore unused/unassigned variable warnings -#pragma warning disable CS0649 -#pragma warning disable CS0169 - -using Newtonsoft.Json; -using System; - -namespace Discord.Net.API -{ - //User - internal class UserReference - { - [JsonProperty(PropertyName = "username")] - public string Username; - [JsonProperty(PropertyName = "id")] - public string Id; - [JsonProperty(PropertyName = "discriminator")] - public string Discriminator; - [JsonProperty(PropertyName = "avatar")] - public string Avatar; - } - internal class SelfUserInfo : UserReference - { - [JsonProperty(PropertyName = "email")] - public string Email; - [JsonProperty(PropertyName = "verified")] - public bool IsVerified; - } - - //Members - internal class MemberReference - { - [JsonProperty(PropertyName = "user_id")] - public string UserId; - [JsonProperty(PropertyName = "user")] - public UserReference User; - [JsonProperty(PropertyName = "guild_id")] - public string GuildId; - } - internal class MemberInfo : MemberReference - { - [JsonProperty(PropertyName = "joined_at")] - public DateTime? JoinedAt; - [JsonProperty(PropertyName = "roles")] - public string[] Roles; - } - internal class ExtendedMemberInfo : MemberInfo - { - [JsonProperty(PropertyName = "mute")] - public bool IsMuted; - [JsonProperty(PropertyName = "deaf")] - public bool IsDeafened; - } - internal class PresenceMemberInfo : MemberReference - { - [JsonProperty(PropertyName = "game_id")] - public string GameId; - [JsonProperty(PropertyName = "status")] - public string Status; - } - internal class VoiceMemberInfo : MemberReference - { - [JsonProperty(PropertyName = "channel_id")] - public string ChannelId; - [JsonProperty(PropertyName = "suppress")] - public bool? IsSuppressed; - [JsonProperty(PropertyName = "session_id")] - public string SessionId; - [JsonProperty(PropertyName = "self_mute")] - public bool? IsSelfMuted; - [JsonProperty(PropertyName = "self_deaf")] - public bool? IsSelfDeafened; - [JsonProperty(PropertyName = "mute")] - public bool IsMuted; - [JsonProperty(PropertyName = "deaf")] - public bool IsDeafened; - [JsonProperty(PropertyName = "token")] - public string Token; - } - - //Channels - internal class ChannelReference - { - [JsonProperty(PropertyName = "id")] - public string Id; - [JsonProperty(PropertyName = "guild_id")] - public string GuildId; - [JsonProperty(PropertyName = "name")] - public string Name; - [JsonProperty(PropertyName = "type")] - public string Type; - } - internal class ChannelInfo : ChannelReference - { - public sealed class PermissionOverwrite - { - [JsonProperty(PropertyName = "type")] - public string Type; - [JsonProperty(PropertyName = "id")] - public string Id; - [JsonProperty(PropertyName = "deny")] - public uint Deny; - [JsonProperty(PropertyName = "allow")] - public uint Allow; - } - - [JsonProperty(PropertyName = "last_message_id")] - public string LastMessageId; - [JsonProperty(PropertyName = "is_private")] - public bool IsPrivate; - [JsonProperty(PropertyName = "position")] - public int Position; - [JsonProperty(PropertyName = "permission_overwrites")] - public PermissionOverwrite[] PermissionOverwrites; - [JsonProperty(PropertyName = "recipient")] - public UserReference Recipient; - } - - //Guilds (Servers) - internal class GuildReference - { - [JsonProperty(PropertyName = "id")] - public string Id; - [JsonProperty(PropertyName = "name")] - public string Name; - } - internal class GuildInfo : GuildReference - { - [JsonProperty(PropertyName = "afk_channel_id")] - public string AFKChannelId; - [JsonProperty(PropertyName = "afk_timeout")] - public int AFKTimeout; - [JsonProperty(PropertyName = "embed_channel_id")] - public string EmbedChannelId; - [JsonProperty(PropertyName = "embed_enabled")] - public bool EmbedEnabled; - [JsonProperty(PropertyName = "icon")] - public string Icon; - [JsonProperty(PropertyName = "joined_at")] - public DateTime? JoinedAt; - [JsonProperty(PropertyName = "owner_id")] - public string OwnerId; - [JsonProperty(PropertyName = "region")] - public string Region; - [JsonProperty(PropertyName = "roles")] - public RoleInfo[] Roles; - } - internal class ExtendedGuildInfo : GuildInfo - { - [JsonProperty(PropertyName = "channels")] - public ChannelInfo[] Channels; - [JsonProperty(PropertyName = "members")] - public ExtendedMemberInfo[] Members; - [JsonProperty(PropertyName = "presences")] - public PresenceMemberInfo[] Presences; - [JsonProperty(PropertyName = "voice_states")] - public VoiceMemberInfo[] VoiceStates; - } - - //Messages - internal class MessageReference - { - [JsonProperty(PropertyName = "id")] - public string Id; - [JsonProperty(PropertyName = "channel_id")] - public string ChannelId; - [JsonProperty(PropertyName = "message_id")] - public string MessageId { get { return Id; } set { Id = value; } } - } - internal class Message : MessageReference - { - public sealed class Attachment - { - [JsonProperty(PropertyName = "id")] - public string Id; - [JsonProperty(PropertyName = "url")] - public string Url; - [JsonProperty(PropertyName = "proxy_url")] - public string ProxyUrl; - [JsonProperty(PropertyName = "size")] - public int Size; - [JsonProperty(PropertyName = "filename")] - public string Filename; - [JsonProperty(PropertyName = "width")] - public int Width; - [JsonProperty(PropertyName = "height")] - public int Height; - } - public sealed class Embed - { - public sealed class Reference - { - [JsonProperty(PropertyName = "url")] - public string Url; - [JsonProperty(PropertyName = "name")] - public string Name; - } - public sealed class ThumbnailInfo - { - [JsonProperty(PropertyName = "url")] - public string Url; - [JsonProperty(PropertyName = "proxy_url")] - public string ProxyUrl; - [JsonProperty(PropertyName = "width")] - public int Width; - [JsonProperty(PropertyName = "height")] - public int Height; - } - - [JsonProperty(PropertyName = "url")] - public string Url; - [JsonProperty(PropertyName = "type")] - public string Type; - [JsonProperty(PropertyName = "title")] - public string Title; - [JsonProperty(PropertyName = "description")] - public string Description; - [JsonProperty(PropertyName = "author")] - public Reference Author; - [JsonProperty(PropertyName = "provider")] - public Reference Provider; - [JsonProperty(PropertyName = "thumbnail")] - public ThumbnailInfo Thumbnail; - } - - [JsonProperty(PropertyName = "tts")] - public bool IsTextToSpeech; - [JsonProperty(PropertyName = "mention_everyone")] - public bool IsMentioningEveryone; - [JsonProperty(PropertyName = "timestamp")] - public DateTime Timestamp; - [JsonProperty(PropertyName = "edited_timestamp")] - public DateTime? EditedTimestamp; - [JsonProperty(PropertyName = "mentions")] - public UserReference[] Mentions; - [JsonProperty(PropertyName = "embeds")] - public Embed[] Embeds; //TODO: Parse this - [JsonProperty(PropertyName = "attachments")] - public Attachment[] Attachments; - [JsonProperty(PropertyName = "content")] - public string Content; - [JsonProperty(PropertyName = "author")] - public UserReference Author; - [JsonProperty(PropertyName = "nonce")] - public string Nonce; - } - - //Roles - internal class RoleReference - { - [JsonProperty(PropertyName = "guild_id")] - public string GuildId; - [JsonProperty(PropertyName = "role_id")] - public string RoleId; - } - internal class RoleInfo - { - [JsonProperty(PropertyName = "permissions")] - public int Permissions; - [JsonProperty(PropertyName = "name")] - public string Name; - [JsonProperty(PropertyName = "id")] - public string Id; - } - - //Invites - internal class Invite - { - [JsonProperty(PropertyName = "inviter")] - public UserReference Inviter; - [JsonProperty(PropertyName = "guild")] - public GuildReference Guild; - [JsonProperty(PropertyName = "channel")] - public ChannelReference Channel; - [JsonProperty(PropertyName = "code")] - public string Code; - [JsonProperty(PropertyName = "xkcdpass")] - public string XkcdPass; - } - internal class ExtendedInvite : Invite - { - [JsonProperty(PropertyName = "max_age")] - public int MaxAge; - [JsonProperty(PropertyName = "max_uses")] - public int MaxUses; - [JsonProperty(PropertyName = "revoked")] - public bool IsRevoked; - [JsonProperty(PropertyName = "temporary")] - public bool IsTemporary; - [JsonProperty(PropertyName = "uses")] - public int Uses; - [JsonProperty(PropertyName = "created_at")] - public DateTime CreatedAt; - } -} diff --git a/src/Discord.Net/Net/API/DiscordAPIClient.cs b/src/Discord.Net/Net/API/DiscordAPIClient.cs deleted file mode 100644 index dfe2f4e45..000000000 --- a/src/Discord.Net/Net/API/DiscordAPIClient.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Discord.Net.API -{ - internal class DiscordAPIClient - { - public const int MaxMessageSize = 2000; - - public RestClient RestClient => _rest; - private readonly RestClient _rest; - - public DiscordAPIClient(LogMessageSeverity logLevel, int timeout) - { - _rest = new RestClient(logLevel, timeout); - } - - private string _token; - public string Token - { - get { return _token; } - set { _token = value; _rest.SetToken(value); } - } - private CancellationToken _cancelToken; - public CancellationToken CancelToken - { - get { return _cancelToken; } - set { _cancelToken = value; _rest.SetCancelToken(value); } - } - - //Auth - public Task GetWebSocketEndpoint() - => _rest.Get(Endpoints.Gateway); - public async Task LoginAnonymous(string username) - { - var fingerprintResponse = await _rest.Post(Endpoints.AuthFingerprint).ConfigureAwait(false); - var registerRequest = new Requests.AuthRegister { Fingerprint = fingerprintResponse.Fingerprint, Username = username }; - var registerResponse = await _rest.Post(Endpoints.AuthRegister, registerRequest).ConfigureAwait(false); - return registerResponse; - } - public async Task Login(string email, string password) - { - var request = new Requests.AuthLogin { Email = email, Password = password }; - var response = await _rest.Post(Endpoints.AuthLogin, request).ConfigureAwait(false); - return response; - } - public Task Logout() - => _rest.Post(Endpoints.AuthLogout); - - //Servers - public Task CreateServer(string name, string region) - { - var request = new Requests.CreateServer { Name = name, Region = region }; - return _rest.Post(Endpoints.Servers, request); - } - public Task LeaveServer(string id) - => _rest.Delete(Endpoints.Server(id)); - - //Channels - public Task CreateChannel(string serverId, string name, string channelType) - { - var request = new Requests.CreateChannel { Name = name, Type = channelType }; - return _rest.Post(Endpoints.ServerChannels(serverId), request); - } - public Task CreatePMChannel(string myId, string recipientId) - { - var request = new Requests.CreatePMChannel { RecipientId = recipientId }; - return _rest.Post(Endpoints.UserChannels(myId), request); - } - public Task DestroyChannel(string channelId) - => _rest.Delete(Endpoints.Channel(channelId)); - public Task GetMessages(string channelId, int count) - => _rest.Get(Endpoints.ChannelMessages(channelId, count)); - - //Members - public Task Kick(string serverId, string userId) - => _rest.Delete(Endpoints.ServerMember(serverId, userId)); - public Task Ban(string serverId, string userId) - => _rest.Put(Endpoints.ServerBan(serverId, userId)); - public Task Unban(string serverId, string userId) - => _rest.Delete(Endpoints.ServerBan(serverId, userId)); - public Task SetMemberRoles(string serverId, string userId, string[] roles) - { - var request = new Requests.ModifyMember { Roles = roles }; - return _rest.Patch(Endpoints.ServerMember(serverId, userId)); - } - - //Invites - public Task CreateInvite(string channelId, int maxAge, int maxUses, bool isTemporary, bool withXkcdPass) - { - var request = new Requests.CreateInvite { MaxAge = maxAge, MaxUses = maxUses, IsTemporary = isTemporary, WithXkcdPass = withXkcdPass }; - return _rest.Post(Endpoints.ChannelInvites(channelId), request); - } - public Task GetInvite(string id) - => _rest.Get(Endpoints.Invite(id)); - public Task AcceptInvite(string id) - => _rest.Post(Endpoints.Invite(id)); - public Task DeleteInvite(string id) - => _rest.Delete(Endpoints.Invite(id)); - - //Roles - public Task CreateRole(string serverId) - { - //TODO: Return a result when Discord starts giving us one - return _rest.Post(Endpoints.ServerRoles(serverId)); - } - public Task RenameRole(string serverId, string roleId, string newName) - { - var request = new Requests.ModifyRole { Name = newName }; - return _rest.Patch(Endpoints.ServerRole(serverId, roleId), request); - } - public Task SetRolePermissions(string serverId, string roleId, PackedPermissions permissions) - { - var request = new Requests.ModifyRole { Permissions = permissions.RawValue }; - return _rest.Patch(Endpoints.ServerRole(serverId, roleId), request); - } - public Task DeleteRole(string serverId, string roleId) - { - return _rest.Delete(Endpoints.ServerRole(serverId, roleId)); - } - - //Permissions - public Task SetChannelPermissions(string channelId, string userOrRoleId, string idType, PackedPermissions allow, PackedPermissions deny) - { - var request = new Requests.SetChannelPermissions { Id = userOrRoleId, Type = idType, Allow = allow.RawValue, Deny = deny.RawValue }; - return _rest.Put(Endpoints.ChannelPermission(channelId, userOrRoleId), request); - } - public Task DeleteChannelPermissions(string channelId, string userOrRoleId) - { - return _rest.Delete(Endpoints.ChannelPermission(channelId, userOrRoleId), null); - } - - //Chat - public Task SendMessage(string channelId, string message, string[] mentions, string nonce, bool isTTS) - { - var request = new Requests.SendMessage { Content = message, Mentions = mentions, Nonce = nonce, IsTTS = isTTS }; - return _rest.Post(Endpoints.ChannelMessages(channelId), request); - } - public Task EditMessage(string messageId, string channelId, string message, string[] mentions) - { - var request = new Requests.EditMessage { Content = message, Mentions = mentions }; - return _rest.Patch(Endpoints.ChannelMessage(channelId, messageId), request); - } - public Task SendIsTyping(string channelId) - => _rest.Post(Endpoints.ChannelTyping(channelId)); - public Task DeleteMessage(string channelId, string msgId) - => _rest.Delete(Endpoints.ChannelMessage(channelId, msgId)); - public Task SendFile(string channelId, string filePath) - => _rest.PostFile(Endpoints.ChannelMessages(channelId), filePath); - - //Voice - public Task GetVoiceRegions() - => _rest.Get(Endpoints.VoiceRegions); - public Task GetVoiceIce() - => _rest.Get(Endpoints.VoiceIce); - public Task Mute(string serverId, string memberId) - { - var request = new Requests.SetMemberMute { Value = true }; - return _rest.Patch(Endpoints.ServerMember(serverId, memberId)); - } - public Task Unmute(string serverId, string memberId) - { - var request = new Requests.SetMemberMute { Value = false }; - return _rest.Patch(Endpoints.ServerMember(serverId, memberId)); - } - public Task Deafen(string serverId, string memberId) - { - var request = new Requests.SetMemberDeaf { Value = true }; - return _rest.Patch(Endpoints.ServerMember(serverId, memberId)); - } - public Task Undeafen(string serverId, string memberId) - { - var request = new Requests.SetMemberDeaf { Value = false }; - return _rest.Patch(Endpoints.ServerMember(serverId, memberId)); - } - - //Profile - public Task ChangeUsername(string newUsername, string currentEmail, string currentPassword) - { - var request = new Requests.ChangeUsername { Username = newUsername, CurrentEmail = currentEmail, CurrentPassword = currentPassword }; - return _rest.Patch(Endpoints.UserMe, request); - } - public Task ChangeEmail(string newEmail, string currentPassword) - { - var request = new Requests.ChangeEmail { NewEmail = newEmail, CurrentPassword = currentPassword }; - return _rest.Patch(Endpoints.UserMe, request); - } - public Task ChangePassword(string newPassword, string currentEmail, string currentPassword) - { - var request = new Requests.ChangePassword { NewPassword = newPassword, CurrentEmail = currentEmail, CurrentPassword = currentPassword }; - return _rest.Patch(Endpoints.UserMe, request); - } - public Task ChangeAvatar(AvatarImageType imageType, byte[] bytes, string currentEmail, string currentPassword) - { - string base64 = Convert.ToBase64String(bytes); - string type = imageType == AvatarImageType.Jpeg ? "image/jpeg;base64" : "image/png;base64"; - var request = new Requests.ChangeAvatar { Avatar = $"data:{type},/9j/{base64}", CurrentEmail = currentEmail, CurrentPassword = currentPassword }; - return _rest.Patch(Endpoints.UserMe, request); - } - - //Other - /*public Task GetUnresolvedIncidents() - { - return _rest.Get(Endpoints.StatusUnresolvedMaintenance); - } - public Task GetActiveIncidents() - { - return _rest.Get(Endpoints.StatusActiveMaintenance); - }*/ - } -} diff --git a/src/Discord.Net/Net/API/Requests.cs b/src/Discord.Net/Net/API/Requests.cs deleted file mode 100644 index 906c676cd..000000000 --- a/src/Discord.Net/Net/API/Requests.cs +++ /dev/null @@ -1,158 +0,0 @@ -//Ignore unused/unassigned variable warnings -#pragma warning disable CS0649 -#pragma warning disable CS0169 - -using Newtonsoft.Json; - -namespace Discord.Net.API -{ - internal static class Requests - { - //Auth - public sealed class AuthRegister - { - [JsonProperty(PropertyName = "fingerprint")] - public string Fingerprint; - [JsonProperty(PropertyName = "username")] - public string Username; - } - public sealed class AuthLogin - { - [JsonProperty(PropertyName = "email")] - public string Email; - [JsonProperty(PropertyName = "password")] - public string Password; - } - - //Servers - public sealed class CreateServer - { - [JsonProperty(PropertyName = "name")] - public string Name; - [JsonProperty(PropertyName = "region")] - public string Region; - } - - //Channels - public sealed class CreateChannel - { - [JsonProperty(PropertyName = "name")] - public string Name; - [JsonProperty(PropertyName = "type")] - public string Type; - } - public sealed class CreatePMChannel - { - [JsonProperty(PropertyName = "recipient_id")] - public string RecipientId; - } - - //Invites - public sealed class CreateInvite - { - [JsonProperty(PropertyName = "max_age")] - public int MaxAge; - [JsonProperty(PropertyName = "max_uses")] - public int MaxUses; - [JsonProperty(PropertyName = "temporary")] - public bool IsTemporary; - [JsonProperty(PropertyName = "xkcdpass")] - public bool WithXkcdPass; - } - - //Messages - public sealed class SendMessage - { - [JsonProperty(PropertyName = "content")] - public string Content; - [JsonProperty(PropertyName = "mentions")] - public string[] Mentions; - [JsonProperty(PropertyName = "nonce")] - public string Nonce; - [JsonProperty(PropertyName = "tts")] - public bool IsTTS; - } - public sealed class EditMessage - { - [JsonProperty(PropertyName = "content")] - public string Content; - [JsonProperty(PropertyName = "mentions")] - public string[] Mentions; - } - - //Members - public sealed class SetMemberMute - { - [JsonProperty(PropertyName = "mute")] - public bool Value; - } - public sealed class SetMemberDeaf - { - [JsonProperty(PropertyName = "deaf")] - public bool Value; - } - public sealed class ModifyMember - { - [JsonProperty(PropertyName = "roles")] - public string[] Roles; - } - - //Profile - public sealed class ChangeUsername - { - [JsonProperty(PropertyName = "email")] - public string CurrentEmail; - [JsonProperty(PropertyName = "password")] - public string CurrentPassword; - [JsonProperty(PropertyName = "username")] - public string Username; - } - public sealed class ChangeEmail - { - [JsonProperty(PropertyName = "email")] - public string NewEmail; - [JsonProperty(PropertyName = "password")] - public string CurrentPassword; - } - public sealed class ChangePassword - { - [JsonProperty(PropertyName = "email")] - public string CurrentEmail; - [JsonProperty(PropertyName = "password")] - public string CurrentPassword; - [JsonProperty(PropertyName = "new_password")] - public string NewPassword; - } - public sealed class ChangeAvatar - { - [JsonProperty(PropertyName = "email")] - public string CurrentEmail; - [JsonProperty(PropertyName = "password")] - public string CurrentPassword; - [JsonProperty(PropertyName = "avatar")] - public string Avatar; - } - - //Roles - public sealed class ModifyRole - { - [JsonProperty(PropertyName = "name", NullValueHandling = NullValueHandling.Ignore)] - public string Name; - [JsonProperty(PropertyName = "permissions", NullValueHandling = NullValueHandling.Ignore)] - public uint Permissions; - } - - //Permissions - public sealed class SetChannelPermissions - { - [JsonProperty(PropertyName = "id")] - public string Id; - [JsonProperty(PropertyName = "type")] - public string Type; - [JsonProperty(PropertyName = "allow")] - public uint Allow; - [JsonProperty(PropertyName = "deny")] - public uint Deny; - } - } -} diff --git a/src/Discord.Net/Net/API/Responses.cs b/src/Discord.Net/Net/API/Responses.cs deleted file mode 100644 index b4d07dc73..000000000 --- a/src/Discord.Net/Net/API/Responses.cs +++ /dev/null @@ -1,85 +0,0 @@ -//Ignore unused/unassigned variable warnings -#pragma warning disable CS0649 -#pragma warning disable CS0169 - -using Newtonsoft.Json; -using System; - -namespace Discord.Net.API -{ - internal static class Responses - { - //Auth - public sealed class Gateway - { - [JsonProperty(PropertyName = "url")] - public string Url; - } - public sealed class AuthFingerprint - { - [JsonProperty(PropertyName = "fingerprint")] - public string Fingerprint; - } - public sealed class AuthRegister - { - [JsonProperty(PropertyName = "token")] - public string Token; - } - public sealed class AuthLogin - { - [JsonProperty(PropertyName = "token")] - public string Token; - } - - //Users - public sealed class ChangeProfile : SelfUserInfo { } - - //Servers - public sealed class CreateServer : GuildInfo { } - public sealed class DeleteServer : GuildInfo { } - - //Channels - public sealed class CreateChannel : ChannelInfo { } - public sealed class DestroyChannel : ChannelInfo { } - - //Invites - public sealed class CreateInvite : ExtendedInvite { } - public sealed class GetInvite : Invite { } - public sealed class AcceptInvite : Invite { } - - //Messages - public sealed class SendMessage : Message { } - public sealed class EditMessage : Message { } - public sealed class GetMessages : Message { } - - //Voice - public sealed class GetRegions - { - [JsonProperty(PropertyName = "sample_hostname")] - public string Hostname; - [JsonProperty(PropertyName = "sample_port")] - public int Port; - [JsonProperty(PropertyName = "id")] - public string Id; - [JsonProperty(PropertyName = "name")] - public string Name; - } - public sealed class GetIce - { - [JsonProperty(PropertyName = "ttl")] - public string TTL; - [JsonProperty(PropertyName = "servers")] - public Server[] Servers; - - public sealed class Server - { - [JsonProperty(PropertyName = "url")] - public string URL; - [JsonProperty(PropertyName = "username")] - public string Username; - [JsonProperty(PropertyName = "credential")] - public string Credential; - } - } - } -} diff --git a/src/Discord.Net/Net/WebSockets/Commands.cs b/src/Discord.Net/Net/WebSockets/Commands.cs deleted file mode 100644 index 00a725a6c..000000000 --- a/src/Discord.Net/Net/WebSockets/Commands.cs +++ /dev/null @@ -1,71 +0,0 @@ -//Ignore unused/unassigned variable warnings -#pragma warning disable CS0649 -#pragma warning disable CS0169 - -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; - -namespace Discord.Net.WebSockets -{ - internal static class Commands - { - public sealed class KeepAlive : WebSocketMessage - { - public KeepAlive() : base(1, GetTimestamp()) { } - private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - private static ulong GetTimestamp() => (ulong)(DateTime.UtcNow - epoch).TotalMilliseconds; - } - public sealed class Login : WebSocketMessage - { - public Login() : base(2) { } - public class Data - { - [JsonProperty(PropertyName = "token")] - public string Token; - [JsonProperty(PropertyName = "v")] - public int Version = 3; - [JsonProperty(PropertyName = "properties")] - public Dictionary Properties = new Dictionary(); - } - } - public sealed class UpdateStatus : WebSocketMessage - { - public UpdateStatus() : base(3) { } - public class Data - { - [JsonProperty(PropertyName = "idle_since")] - public string IdleSince; - [JsonProperty(PropertyName = "game_id")] - public string GameId; - } - } - public sealed class JoinVoice : WebSocketMessage - { - public JoinVoice() : base(4) { } - public class Data - { - [JsonProperty(PropertyName = "guild_id")] - public string ServerId; - [JsonProperty(PropertyName = "channel_id")] - public string ChannelId; - [JsonProperty(PropertyName = "self_mute")] - public string SelfMute; - [JsonProperty(PropertyName = "self_deaf")] - public string SelfDeaf; - } - } - public sealed class Resume : WebSocketMessage - { - public Resume() : base(6) { } - public class Data - { - [JsonProperty(PropertyName = "session_id")] - public string SessionId; - [JsonProperty(PropertyName = "seq")] - public int Sequence; - } - } - } -} diff --git a/src/Discord.Net/Net/WebSockets/Events.cs b/src/Discord.Net/Net/WebSockets/Events.cs deleted file mode 100644 index e1cd54184..000000000 --- a/src/Discord.Net/Net/WebSockets/Events.cs +++ /dev/null @@ -1,118 +0,0 @@ -//Ignore unused/unassigned variable warnings -#pragma warning disable CS0649 -#pragma warning disable CS0169 - -using Discord.Net.API; -using Newtonsoft.Json; - -namespace Discord.Net.WebSockets -{ - internal static class Events - { - public sealed class Ready - { - public sealed class ReadStateInfo - { - [JsonProperty(PropertyName = "id")] - public string ChannelId; - [JsonProperty(PropertyName = "mention_count")] - public int MentionCount; - [JsonProperty(PropertyName = "last_message_id")] - public string LastMessageId; - } - - [JsonProperty(PropertyName = "v")] - public int Version; - [JsonProperty(PropertyName = "user")] - public SelfUserInfo User; - [JsonProperty(PropertyName = "session_id")] - public string SessionId; - [JsonProperty(PropertyName = "read_state")] - public ReadStateInfo[] ReadState; - [JsonProperty(PropertyName = "guilds")] - public ExtendedGuildInfo[] Guilds; - [JsonProperty(PropertyName = "private_channels")] - public ChannelInfo[] PrivateChannels; - [JsonProperty(PropertyName = "heartbeat_interval")] - public int HeartbeatInterval; - } - public sealed class Resumed - { - [JsonProperty(PropertyName = "heartbeat_interval")] - public int HeartbeatInterval; - } - - public sealed class Redirect - { - [JsonProperty(PropertyName = "url")] - public string Url; - } - - //Servers - public sealed class GuildCreate : ExtendedGuildInfo { } - public sealed class GuildUpdate : GuildInfo { } - public sealed class GuildDelete : ExtendedGuildInfo { } - - //Channels - public sealed class ChannelCreate : ChannelInfo { } - public sealed class ChannelDelete : ChannelInfo { } - public sealed class ChannelUpdate : ChannelInfo { } - - //Memberships - public sealed class GuildMemberAdd : MemberInfo { } - public sealed class GuildMemberUpdate : MemberInfo { } - public sealed class GuildMemberRemove : MemberInfo { } - - //Roles - public sealed class GuildRoleCreate - { - [JsonProperty(PropertyName = "guild_id")] - public string GuildId; - [JsonProperty(PropertyName = "role")] - public RoleInfo Data; - } - public sealed class GuildRoleUpdate - { - [JsonProperty(PropertyName = "guild_id")] - public string GuildId; - [JsonProperty(PropertyName = "role")] - public RoleInfo Data; - } - public sealed class GuildRoleDelete : RoleReference { } - - //Bans - public sealed class GuildBanAdd : MemberReference { } - public sealed class GuildBanRemove : MemberReference { } - - //User - public sealed class UserUpdate : SelfUserInfo { } - public sealed class PresenceUpdate : PresenceMemberInfo { } - public sealed class VoiceStateUpdate : VoiceMemberInfo { } - - //Chat - public sealed class MessageCreate : API.Message { } - public sealed class MessageUpdate : API.Message { } - public sealed class MessageDelete : MessageReference { } - public sealed class MessageAck : MessageReference { } - public sealed class TypingStart - { - [JsonProperty(PropertyName = "user_id")] - public string UserId; - [JsonProperty(PropertyName = "channel_id")] - public string ChannelId; - [JsonProperty(PropertyName = "timestamp")] - public int Timestamp; - } - - //Voice - public sealed class VoiceServerUpdate - { - [JsonProperty(PropertyName = "guild_id")] - public string GuildId; - [JsonProperty(PropertyName = "endpoint")] - public string Endpoint; - [JsonProperty(PropertyName = "token")] - public string Token; - } - } -} diff --git a/src/Discord.Net/Net/WebSockets/VoiceCommands.cs b/src/Discord.Net/Net/WebSockets/VoiceCommands.cs deleted file mode 100644 index 9b7ac1a14..000000000 --- a/src/Discord.Net/Net/WebSockets/VoiceCommands.cs +++ /dev/null @@ -1,62 +0,0 @@ -//Ignore unused/unassigned variable warnings -#pragma warning disable CS0649 -#pragma warning disable CS0169 - -using Newtonsoft.Json; - -namespace Discord.Net.WebSockets -{ - internal static class VoiceCommands - { - public sealed class Login : WebSocketMessage - { - public Login() : base(0) { } - public class Data - { - [JsonProperty(PropertyName = "server_id")] - public string ServerId; - [JsonProperty(PropertyName = "user_id")] - public string UserId; - [JsonProperty(PropertyName = "session_id")] - public string SessionId; - [JsonProperty(PropertyName = "token")] - public string Token; - } - } - public sealed class Login2 : WebSocketMessage - { - public Login2() : base(1) { } - public class Data - { - public class SocketInfo - { - [JsonProperty(PropertyName = "address")] - public string Address; - [JsonProperty(PropertyName = "port")] - public int Port; - [JsonProperty(PropertyName = "mode")] - public string Mode = "xsalsa20_poly1305"; - } - [JsonProperty(PropertyName = "protocol")] - public string Protocol = "udp"; - [JsonProperty(PropertyName = "data")] - public SocketInfo SocketData = new SocketInfo(); - } - } - public sealed class KeepAlive : WebSocketMessage - { - public KeepAlive() : base(3, null) { } - } - public sealed class IsTalking : WebSocketMessage - { - public IsTalking() : base(5) { } - public class Data - { - [JsonProperty(PropertyName = "delay")] - public int Delay; - [JsonProperty(PropertyName = "speaking")] - public bool IsSpeaking; - } - } - } -} diff --git a/src/Discord.Net/Net/WebSockets/VoiceEvents.cs b/src/Discord.Net/Net/WebSockets/VoiceEvents.cs deleted file mode 100644 index 8f17a7b11..000000000 --- a/src/Discord.Net/Net/WebSockets/VoiceEvents.cs +++ /dev/null @@ -1,41 +0,0 @@ -//Ignore unused/unassigned variable warnings -#pragma warning disable CS0649 -#pragma warning disable CS0169 - -using Newtonsoft.Json; - -namespace Discord.Net.WebSockets -{ - internal static class VoiceEvents - { - public sealed class Ready - { - [JsonProperty(PropertyName = "ssrc")] - public uint SSRC; - [JsonProperty(PropertyName = "port")] - public ushort Port; - [JsonProperty(PropertyName = "modes")] - public string[] Modes; - [JsonProperty(PropertyName = "heartbeat_interval")] - public int HeartbeatInterval; - } - - public sealed class JoinServer - { - [JsonProperty(PropertyName = "secret_key")] - public byte[] SecretKey; - [JsonProperty(PropertyName = "mode")] - public string Mode; - } - - public sealed class IsTalking - { - [JsonProperty(PropertyName = "user_id")] - public string UserId; - [JsonProperty(PropertyName = "ssrc")] - public uint SSRC; - [JsonProperty(PropertyName = "speaking")] - public bool IsSpeaking; - } - } -} diff --git a/src/Discord.Net/TimeoutException.cs b/src/Discord.Net/TimeoutException.cs index 5c35374c7..3ae2f072a 100644 --- a/src/Discord.Net/TimeoutException.cs +++ b/src/Discord.Net/TimeoutException.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord { diff --git a/src/Discord.Net/WebSockets/Data/Commands.cs b/src/Discord.Net/WebSockets/Data/Commands.cs new file mode 100644 index 000000000..e0d3d86c3 --- /dev/null +++ b/src/Discord.Net/WebSockets/Data/Commands.cs @@ -0,0 +1,67 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace Discord.WebSockets.Data +{ + internal sealed class KeepAliveCommand : WebSocketMessage + { + public KeepAliveCommand() : base(1, GetTimestamp()) { } + private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private static ulong GetTimestamp() => (ulong)(DateTime.UtcNow - epoch).TotalMilliseconds; + } + internal sealed class LoginCommand : WebSocketMessage + { + public LoginCommand() : base(2) { } + public class Data + { + [JsonProperty("token")] + public string Token; + [JsonProperty("v")] + public int Version = 3; + [JsonProperty("properties")] + public Dictionary Properties = new Dictionary(); + } + } + internal sealed class UpdateStatusCommand : WebSocketMessage + { + public UpdateStatusCommand() : base(3) { } + public class Data + { + [JsonProperty("idle_since")] + public string IdleSince; + [JsonProperty("game_id")] + public string GameId; + } + } + internal sealed class JoinVoiceCommand : WebSocketMessage + { + public JoinVoiceCommand() : base(4) { } + public class Data + { + [JsonProperty("guild_id")] + public string ServerId; + [JsonProperty("channel_id")] + public string ChannelId; + [JsonProperty("self_mute")] + public string SelfMute; + [JsonProperty("self_deaf")] + public string SelfDeaf; + } + } + internal sealed class ResumeCommand : WebSocketMessage + { + public ResumeCommand() : base(6) { } + public class Data + { + [JsonProperty("session_id")] + public string SessionId; + [JsonProperty("seq")] + public int Sequence; + } + } +} diff --git a/src/Discord.Net/Net/WebSockets/DataWebSocket.cs b/src/Discord.Net/WebSockets/Data/DataWebSocket.cs similarity index 84% rename from src/Discord.Net/Net/WebSockets/DataWebSocket.cs rename to src/Discord.Net/WebSockets/Data/DataWebSocket.cs index 2f5effb9c..659d61e37 100644 --- a/src/Discord.Net/Net/WebSockets/DataWebSocket.cs +++ b/src/Discord.Net/WebSockets/Data/DataWebSocket.cs @@ -1,10 +1,9 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; -using System.Threading; using System.Threading.Tasks; -namespace Discord.Net.WebSockets +namespace Discord.WebSockets.Data { internal partial class DataWebSocket : WebSocket { @@ -22,7 +21,7 @@ namespace Discord.Net.WebSockets { await Connect().ConfigureAwait(false); - Commands.Login msg = new Commands.Login(); + LoginCommand msg = new LoginCommand(); msg.Payload.Token = token; msg.Payload.Properties["$device"] = "Discord.Net"; QueueMessage(msg); @@ -32,7 +31,7 @@ namespace Discord.Net.WebSockets await DisconnectInternal(isUnexpected: false).ConfigureAwait(false); await Connect().ConfigureAwait(false); - var resumeMsg = new Commands.Resume(); + var resumeMsg = new ResumeCommand(); resumeMsg.Payload.SessionId = _sessionId; resumeMsg.Payload.Sequence = _lastSeq; QueueMessage(resumeMsg); @@ -75,16 +74,16 @@ namespace Discord.Net.WebSockets JToken token = msg.Payload as JToken; if (msg.Type == "READY") { - var payload = token.ToObject(); + var payload = token.ToObject(); _sessionId = payload.SessionId; _heartbeatInterval = payload.HeartbeatInterval; - QueueMessage(new Commands.UpdateStatus()); + QueueMessage(new UpdateStatusCommand()); } else if (msg.Type == "RESUMED") { - var payload = token.ToObject(); + var payload = token.ToObject(); _heartbeatInterval = payload.HeartbeatInterval; - QueueMessage(new Commands.UpdateStatus()); + QueueMessage(new UpdateStatusCommand()); } RaiseReceivedEvent(msg.Type, token); if (msg.Type == "READY" || msg.Type == "RESUMED") @@ -93,7 +92,7 @@ namespace Discord.Net.WebSockets break; case 7: //Redirect { - var payload = (msg.Payload as JToken).ToObject(); + var payload = (msg.Payload as JToken).ToObject(); Host = payload.Url; if (_logLevel >= LogMessageSeverity.Info) RaiseOnLog(LogMessageSeverity.Info, "Redirected to " + payload.Url); @@ -109,19 +108,19 @@ namespace Discord.Net.WebSockets protected override object GetKeepAlive() { - return new Commands.KeepAlive(); + return new KeepAliveCommand(); } public void SendJoinVoice(string serverId, string channelId) { - var joinVoice = new Commands.JoinVoice(); + var joinVoice = new JoinVoiceCommand(); joinVoice.Payload.ServerId = serverId; joinVoice.Payload.ChannelId = channelId; QueueMessage(joinVoice); } public void SendLeaveVoice(string serverId) { - var leaveVoice = new Commands.JoinVoice(); + var leaveVoice = new JoinVoiceCommand(); leaveVoice.Payload.ServerId = serverId; QueueMessage(leaveVoice); } diff --git a/src/Discord.Net/Net/WebSockets/DataWebSockets.Events.cs b/src/Discord.Net/WebSockets/Data/DataWebSockets.Events.cs similarity index 84% rename from src/Discord.Net/Net/WebSockets/DataWebSockets.Events.cs rename to src/Discord.Net/WebSockets/Data/DataWebSockets.Events.cs index 0cee287f2..0212fbce3 100644 --- a/src/Discord.Net/Net/WebSockets/DataWebSockets.Events.cs +++ b/src/Discord.Net/WebSockets/Data/DataWebSockets.Events.cs @@ -1,9 +1,9 @@ using Newtonsoft.Json.Linq; using System; -namespace Discord.Net.WebSockets +namespace Discord.WebSockets.Data { - public sealed class WebSocketEventEventArgs : EventArgs + internal sealed class WebSocketEventEventArgs : EventArgs { public readonly string Type; public readonly JToken Payload; diff --git a/src/Discord.Net/WebSockets/Data/Events.cs b/src/Discord.Net/WebSockets/Data/Events.cs new file mode 100644 index 000000000..2ed5c4b4b --- /dev/null +++ b/src/Discord.Net/WebSockets/Data/Events.cs @@ -0,0 +1,115 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Discord.API; +using Newtonsoft.Json; + +namespace Discord.WebSockets.Data +{ + internal sealed class ReadyEvent + { + public sealed class ReadStateInfo + { + [JsonProperty("id")] + public string ChannelId; + [JsonProperty("mention_count")] + public int MentionCount; + [JsonProperty("last_message_id")] + public string LastMessageId; + } + + [JsonProperty("v")] + public int Version; + [JsonProperty("user")] + public SelfUserInfo User; + [JsonProperty("session_id")] + public string SessionId; + [JsonProperty("read_state")] + public ReadStateInfo[] ReadState; + [JsonProperty("guilds")] + public ExtendedGuildInfo[] Guilds; + [JsonProperty("private_channels")] + public ChannelInfo[] PrivateChannels; + [JsonProperty("heartbeat_interval")] + public int HeartbeatInterval; + } + internal sealed class ResumedEvent + { + [JsonProperty("heartbeat_interval")] + public int HeartbeatInterval; + } + + internal sealed class RedirectEvent + { + [JsonProperty("url")] + public string Url; + } + + //Servers + internal sealed class GuildCreateEvent : ExtendedGuildInfo { } + internal sealed class GuildUpdateEvent : GuildInfo { } + internal sealed class GuildDeleteEvent : ExtendedGuildInfo { } + + //Channels + internal sealed class ChannelCreateEvent : ChannelInfo { } + internal sealed class ChannelDeleteEvent : ChannelInfo { } + internal sealed class ChannelUpdateEvent : ChannelInfo { } + + //Memberships + internal sealed class GuildMemberAddEvent : MemberInfo { } + internal sealed class GuildMemberUpdateEvent : MemberInfo { } + internal sealed class GuildMemberRemoveEvent : MemberInfo { } + + //Roles + internal sealed class GuildRoleCreateEvent + { + [JsonProperty("guild_id")] + public string GuildId; + [JsonProperty("role")] + public RoleInfo Data; + } + internal sealed class GuildRoleUpdateEvent + { + [JsonProperty("guild_id")] + public string GuildId; + [JsonProperty("role")] + public RoleInfo Data; + } + internal sealed class GuildRoleDeleteEvent : RoleReference { } + + //Bans + internal sealed class GuildBanAddEvent : MemberReference { } + internal sealed class GuildBanRemoveEvent : MemberReference { } + + //User + internal sealed class UserUpdateEvent : SelfUserInfo { } + internal sealed class PresenceUpdateEvent : PresenceMemberInfo { } + internal sealed class VoiceStateUpdateEvent : VoiceMemberInfo { } + + //Chat + internal sealed class MessageCreateEvent : API.Message { } + internal sealed class MessageUpdateEvent : API.Message { } + internal sealed class MessageDeleteEvent : MessageReference { } + internal sealed class MessageAckEvent : MessageReference { } + internal sealed class TypingStartEvent + { + [JsonProperty("user_id")] + public string UserId; + [JsonProperty("channel_id")] + public string ChannelId; + [JsonProperty("timestamp")] + public int Timestamp; + } + + //Voice + internal sealed class VoiceServerUpdateEvent + { + [JsonProperty("guild_id")] + public string GuildId; + [JsonProperty("endpoint")] + public string Endpoint; + [JsonProperty("token")] + public string Token; + } +} diff --git a/src/Discord.Net/WebSockets/Voice/Commands.cs b/src/Discord.Net/WebSockets/Voice/Commands.cs new file mode 100644 index 000000000..740f235bc --- /dev/null +++ b/src/Discord.Net/WebSockets/Voice/Commands.cs @@ -0,0 +1,59 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; + +namespace Discord.WebSockets.Voice +{ + internal sealed class LoginCommand : WebSocketMessage + { + public LoginCommand() : base(0) { } + public class Data + { + [JsonProperty("server_id")] + public string ServerId; + [JsonProperty("user_id")] + public string UserId; + [JsonProperty("session_id")] + public string SessionId; + [JsonProperty("token")] + public string Token; + } + } + internal sealed class Login2Command : WebSocketMessage + { + public Login2Command() : base(1) { } + public class Data + { + public class SocketInfo + { + [JsonProperty("address")] + public string Address; + [JsonProperty("port")] + public int Port; + [JsonProperty("mode")] + public string Mode = "xsalsa20_poly1305"; + } + [JsonProperty("protocol")] + public string Protocol = "udp"; + [JsonProperty("data")] + public SocketInfo SocketData = new SocketInfo(); + } + } + internal sealed class KeepAliveCommand : WebSocketMessage + { + public KeepAliveCommand() : base(3, null) { } + } + internal sealed class IsTalkingCommand : WebSocketMessage + { + public IsTalkingCommand() : base(5) { } + public class Data + { + [JsonProperty("delay")] + public int Delay; + [JsonProperty("speaking")] + public bool IsSpeaking; + } + } +} diff --git a/src/Discord.Net/WebSockets/Voice/Events.cs b/src/Discord.Net/WebSockets/Voice/Events.cs new file mode 100644 index 000000000..a2bd730df --- /dev/null +++ b/src/Discord.Net/WebSockets/Voice/Events.cs @@ -0,0 +1,38 @@ +//Ignore unused/unassigned variable warnings +#pragma warning disable CS0649 +#pragma warning disable CS0169 + +using Newtonsoft.Json; + +namespace Discord.WebSockets.Voice +{ + internal sealed class ReadyEvent + { + [JsonProperty("ssrc")] + public uint SSRC; + [JsonProperty("port")] + public ushort Port; + [JsonProperty("modes")] + public string[] Modes; + [JsonProperty("heartbeat_interval")] + public int HeartbeatInterval; + } + + internal sealed class JoinServerEvent + { + [JsonProperty("secret_key")] + public byte[] SecretKey; + [JsonProperty("mode")] + public string Mode; + } + + internal sealed class IsTalkingEvent + { + [JsonProperty("user_id")] + public string UserId; + [JsonProperty("ssrc")] + public uint SSRC; + [JsonProperty("speaking")] + public bool IsSpeaking; + } +} diff --git a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.Events.cs b/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.Events.cs similarity index 93% rename from src/Discord.Net/Net/WebSockets/VoiceWebSocket.Events.cs rename to src/Discord.Net/WebSockets/Voice/VoiceWebSocket.Events.cs index c97d3937d..de6d43569 100644 --- a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.Events.cs +++ b/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.Events.cs @@ -1,6 +1,6 @@ using System; -namespace Discord.Net.WebSockets +namespace Discord.WebSockets.Voice { public sealed class IsTalkingEventArgs : EventArgs { diff --git a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs b/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.cs similarity index 97% rename from src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs rename to src/Discord.Net/WebSockets/Voice/VoiceWebSocket.cs index 52f76a3c2..054bd0882 100644 --- a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs +++ b/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.cs @@ -12,7 +12,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -namespace Discord.Net.WebSockets +namespace Discord.WebSockets.Voice { internal partial class VoiceWebSocket : WebSocket { @@ -106,7 +106,7 @@ namespace Discord.Net.WebSockets _udp.AllowNatTraversal(true); #endif - VoiceCommands.Login msg = new VoiceCommands.Login(); + LoginCommand msg = new LoginCommand(); msg.Payload.ServerId = _serverId; msg.Payload.SessionId = _sessionId; msg.Payload.Token = _token; @@ -294,7 +294,7 @@ namespace Discord.Net.WebSockets { if (_state != (int)WebSocketState.Connected) { - var payload = (msg.Payload as JToken).ToObject(); + var payload = (msg.Payload as JToken).ToObject(); _heartbeatInterval = payload.HeartbeatInterval; _ssrc = payload.SSRC; _endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(), payload.Port); @@ -319,7 +319,7 @@ namespace Discord.Net.WebSockets break; case 4: //SESSION_DESCRIPTION { - var payload = (msg.Payload as JToken).ToObject(); + var payload = (msg.Payload as JToken).ToObject(); _secretKey = payload.SecretKey; SendIsTalking(true); _connectWaitOnLogin.Set(); @@ -327,7 +327,7 @@ namespace Discord.Net.WebSockets break; case 5: { - var payload = (msg.Payload as JToken).ToObject(); + var payload = (msg.Payload as JToken).ToObject(); RaiseIsSpeaking(payload.UserId, payload.IsSpeaking); } break; @@ -358,7 +358,7 @@ namespace Discord.Net.WebSockets CompleteConnect(); - var login2 = new VoiceCommands.Login2(); + var login2 = new Login2Command(); login2.Payload.Protocol = "udp"; login2.Payload.SocketData.Address = ip; login2.Payload.SocketData.Mode = _isEncrypted ? EncryptedMode : UnencryptedMode; @@ -503,7 +503,7 @@ namespace Discord.Net.WebSockets private void SendIsTalking(bool value) { - var isTalking = new VoiceCommands.IsTalking(); + var isTalking = new IsTalkingCommand(); isTalking.Payload.IsSpeaking = value; isTalking.Payload.Delay = 0; QueueMessage(isTalking); @@ -511,7 +511,7 @@ namespace Discord.Net.WebSockets protected override object GetKeepAlive() { - return new VoiceCommands.KeepAlive(); + return new KeepAliveCommand(); } public void WaitForQueue() diff --git a/src/Discord.Net/Net/WebSockets/WebSocket.BuiltIn.cs b/src/Discord.Net/WebSockets/WebSocket.BuiltIn.cs similarity index 99% rename from src/Discord.Net/Net/WebSockets/WebSocket.BuiltIn.cs rename to src/Discord.Net/WebSockets/WebSocket.BuiltIn.cs index 36b28ca8d..b2b899886 100644 --- a/src/Discord.Net/Net/WebSockets/WebSocket.BuiltIn.cs +++ b/src/Discord.Net/WebSockets/WebSocket.BuiltIn.cs @@ -8,7 +8,7 @@ using System.Threading; using System.Threading.Tasks; using State = System.Net.WebSockets.WebSocketState; -namespace Discord.Net.WebSockets +namespace Discord.WebSockets { internal class BuiltInWebSocketEngine : IWebSocketEngine { diff --git a/src/Discord.Net/Net/WebSockets/WebSocket.Events.cs b/src/Discord.Net/WebSockets/WebSocket.Events.cs similarity index 95% rename from src/Discord.Net/Net/WebSockets/WebSocket.Events.cs rename to src/Discord.Net/WebSockets/WebSocket.Events.cs index 70fab7836..c2ef3e79e 100644 --- a/src/Discord.Net/Net/WebSockets/WebSocket.Events.cs +++ b/src/Discord.Net/WebSockets/WebSocket.Events.cs @@ -1,6 +1,6 @@ using System; -namespace Discord.Net.WebSockets +namespace Discord.WebSockets { internal partial class WebSocket { diff --git a/src/Discord.Net/Net/WebSockets/WebSocket.cs b/src/Discord.Net/WebSockets/WebSocket.cs similarity index 98% rename from src/Discord.Net/Net/WebSockets/WebSocket.cs rename to src/Discord.Net/WebSockets/WebSocket.cs index aa12279c8..258596d79 100644 --- a/src/Discord.Net/Net/WebSockets/WebSocket.cs +++ b/src/Discord.Net/WebSockets/WebSocket.cs @@ -7,7 +7,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -namespace Discord.Net.WebSockets +namespace Discord.WebSockets { public enum WebSocketState : byte { @@ -88,7 +88,7 @@ namespace Discord.Net.WebSockets _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelTokenSource.Token, ParentCancelToken).Token; else _cancelToken = _cancelTokenSource.Token; - + await _engine.Connect(Host, _cancelToken).ConfigureAwait(false); _lastHeartbeat = DateTime.UtcNow; @@ -107,8 +107,6 @@ namespace Discord.Net.WebSockets _connectedEvent.Set(); RaiseConnected(); } - /*public Task Reconnect(CancellationToken cancelToken) - => Connect(_host, _cancelToken);*/ public Task Disconnect() => DisconnectInternal(new Exception("Disconnect was requested by user."), isUnexpected: false); protected async Task DisconnectInternal(Exception ex = null, bool isUnexpected = true, bool skipAwait = false) diff --git a/src/Discord.Net/Net/WebSockets/WebSocketMessage.cs b/src/Discord.Net/WebSockets/WebSocketMessage.cs similarity index 71% rename from src/Discord.Net/Net/WebSockets/WebSocketMessage.cs rename to src/Discord.Net/WebSockets/WebSocketMessage.cs index bbaaf9efe..c63bd4787 100644 --- a/src/Discord.Net/Net/WebSockets/WebSocketMessage.cs +++ b/src/Discord.Net/WebSockets/WebSocketMessage.cs @@ -1,17 +1,17 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace Discord.Net.WebSockets +namespace Discord.WebSockets { public class WebSocketMessage { - [JsonProperty(PropertyName = "op")] + [JsonProperty("op")] public int Operation; - [JsonProperty(PropertyName = "d")] + [JsonProperty("d")] public object Payload; - [JsonProperty(PropertyName = "t", NullValueHandling = NullValueHandling.Ignore)] + [JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)] public string Type; - [JsonProperty(PropertyName = "s", NullValueHandling = NullValueHandling.Ignore)] + [JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)] public int? Sequence; } internal abstract class WebSocketMessage : WebSocketMessage