Browse Source

Refactored project structure and API calls. Exposed DiscordAPIClient as public.

tags/docs-0.9
RogueException 9 years ago
parent
commit
1d485eb641
44 changed files with 1061 additions and 1543 deletions
  1. +1
    -0
      src/Discord.Net.Commands/CommandParser.cs
  2. +67
    -56
      src/Discord.Net.Net45/Discord.Net.csproj
  3. +295
    -0
      src/Discord.Net/API/Common.cs
  4. +2
    -1
      src/Discord.Net/API/Endpoints.cs
  5. +1
    -1
      src/Discord.Net/API/HttpException.cs
  6. +142
    -0
      src/Discord.Net/API/Requests.cs
  7. +0
    -0
      src/Discord.Net/API/RestClient.BuiltIn.cs
  8. +1
    -1
      src/Discord.Net/API/RestClient.Events.cs
  9. +1
    -1
      src/Discord.Net/API/RestClient.SharpRest.cs
  10. +1
    -1
      src/Discord.Net/API/RestClient.cs
  11. +174
    -325
      src/Discord.Net/DiscordClient.API.cs
  12. +2
    -1
      src/Discord.Net/DiscordClient.Voice.cs
  13. +32
    -31
      src/Discord.Net/DiscordClient.cs
  14. +2
    -3
      src/Discord.Net/Format.cs
  15. +1
    -6
      src/Discord.Net/Mention.cs
  16. +4
    -5
      src/Discord.Net/Models/Channel.cs
  17. +5
    -6
      src/Discord.Net/Models/Invite.cs
  18. +8
    -9
      src/Discord.Net/Models/Member.cs
  19. +1
    -1
      src/Discord.Net/Models/Message.cs
  20. +1
    -2
      src/Discord.Net/Models/PackedPermissions.cs
  21. +1
    -2
      src/Discord.Net/Models/Role.cs
  22. +4
    -5
      src/Discord.Net/Models/Server.cs
  23. +5
    -6
      src/Discord.Net/Models/User.cs
  24. +0
    -295
      src/Discord.Net/Net/API/Common.cs
  25. +0
    -212
      src/Discord.Net/Net/API/DiscordAPIClient.cs
  26. +0
    -158
      src/Discord.Net/Net/API/Requests.cs
  27. +0
    -85
      src/Discord.Net/Net/API/Responses.cs
  28. +0
    -71
      src/Discord.Net/Net/WebSockets/Commands.cs
  29. +0
    -118
      src/Discord.Net/Net/WebSockets/Events.cs
  30. +0
    -62
      src/Discord.Net/Net/WebSockets/VoiceCommands.cs
  31. +0
    -41
      src/Discord.Net/Net/WebSockets/VoiceEvents.cs
  32. +0
    -4
      src/Discord.Net/TimeoutException.cs
  33. +67
    -0
      src/Discord.Net/WebSockets/Data/Commands.cs
  34. +11
    -12
      src/Discord.Net/WebSockets/Data/DataWebSocket.cs
  35. +2
    -2
      src/Discord.Net/WebSockets/Data/DataWebSockets.Events.cs
  36. +115
    -0
      src/Discord.Net/WebSockets/Data/Events.cs
  37. +59
    -0
      src/Discord.Net/WebSockets/Voice/Commands.cs
  38. +38
    -0
      src/Discord.Net/WebSockets/Voice/Events.cs
  39. +1
    -1
      src/Discord.Net/WebSockets/Voice/VoiceWebSocket.Events.cs
  40. +8
    -8
      src/Discord.Net/WebSockets/Voice/VoiceWebSocket.cs
  41. +1
    -1
      src/Discord.Net/WebSockets/WebSocket.BuiltIn.cs
  42. +1
    -1
      src/Discord.Net/WebSockets/WebSocket.Events.cs
  43. +2
    -4
      src/Discord.Net/WebSockets/WebSocket.cs
  44. +5
    -5
      src/Discord.Net/WebSockets/WebSocketMessage.cs

+ 1
- 0
src/Discord.Net.Commands/CommandParser.cs View File

@@ -2,6 +2,7 @@


namespace Discord.Commands namespace Discord.Commands
{ {
//TODO: Check support for escaping
public static class CommandParser public static class CommandParser
{ {
private enum CommandParserPart private enum CommandParserPart


+ 67
- 56
src/Discord.Net.Net45/Discord.Net.csproj View File

@@ -35,6 +35,17 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'FullDebug|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\FullDebug\</OutputPath>
<DefineConstants>TRACE;DEBUG;NET45,TEST_RESPONSES</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<WarningLevel>2</WarningLevel>
<DebugType>full</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> <Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\..\DiscordBot\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> <HintPath>..\..\..\DiscordBot\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
@@ -57,6 +68,33 @@
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\Discord.Net\API\Common.cs">
<Link>API\Common.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\API\Endpoints.cs">
<Link>API\Endpoints.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\API\HttpException.cs">
<Link>API\HttpException.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\API\Requests.cs">
<Link>API\Requests.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\API\Responses.cs">
<Link>API\Responses.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\API\RestClient.BuiltIn.cs">
<Link>API\RestClient.BuiltIn.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\API\RestClient.cs">
<Link>API\RestClient.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\API\RestClient.Events.cs">
<Link>API\RestClient.Events.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\API\RestClient.SharpRest.cs">
<Link>API\RestClient.SharpRest.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Audio\Opus.cs"> <Compile Include="..\Discord.Net\Audio\Opus.cs">
<Link>Audio\Opus.cs</Link> <Link>Audio\Opus.cs</Link>
</Compile> </Compile>
@@ -84,6 +122,9 @@
<Compile Include="..\Discord.Net\Collections\Users.cs"> <Compile Include="..\Discord.Net\Collections\Users.cs">
<Link>Collections\Users.cs</Link> <Link>Collections\Users.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\DiscordAPIClient.cs">
<Link>DiscordAPIClient.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\DiscordClient.API.cs"> <Compile Include="..\Discord.Net\DiscordClient.API.cs">
<Link>DiscordClient.API.cs</Link> <Link>DiscordClient.API.cs</Link>
</Compile> </Compile>
@@ -150,74 +191,44 @@
<Compile Include="..\Discord.Net\Models\User.cs"> <Compile Include="..\Discord.Net\Models\User.cs">
<Link>Models\User.cs</Link> <Link>Models\User.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Net\API\Common.cs">
<Link>Net\API\Common.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Net\API\DiscordAPIClient.cs">
<Link>Net\API\DiscordAPIClient.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Net\API\Endpoints.cs">
<Link>Net\API\Endpoints.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Net\API\HttpException.cs">
<Link>Net\API\HttpException.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Net\API\Requests.cs">
<Link>Net\API\Requests.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Net\API\Responses.cs">
<Link>Net\API\Responses.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Net\API\RestClient.BuiltIn.cs">
<Link>Net\API\RestClient.BuiltIn.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Net\API\RestClient.cs">
<Link>Net\API\RestClient.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Net\API\RestClient.Events.cs">
<Link>Net\API\RestClient.Events.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Net\API\RestClient.SharpRest.cs">
<Link>Net\API\RestClient.SharpRest.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Net\WebSockets\Commands.cs">
<Link>Net\WebSockets\Commands.cs</Link>
<Compile Include="..\Discord.Net\TimeoutException.cs">
<Link>TimeoutException.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Net\WebSockets\DataWebSocket.cs">
<Link>Net\WebSockets\DataWebSocket.cs</Link>
<Compile Include="..\Discord.Net\WebSockets\Data\Commands.cs">
<Link>WebSockets\Data\Commands.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Net\WebSockets\DataWebSockets.Events.cs">
<Link>Net\WebSockets\DataWebSockets.Events.cs</Link>
<Compile Include="..\Discord.Net\WebSockets\Data\DataWebSocket.cs">
<Link>WebSockets\Data\DataWebSocket.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Net\WebSockets\Events.cs">
<Link>Net\WebSockets\Events.cs</Link>
<Compile Include="..\Discord.Net\WebSockets\Data\DataWebSockets.Events.cs">
<Link>WebSockets\Data\DataWebSockets.Events.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Net\WebSockets\VoiceCommands.cs">
<Link>Net\WebSockets\VoiceCommands.cs</Link>
<Compile Include="..\Discord.Net\WebSockets\Data\Events.cs">
<Link>WebSockets\Data\Events.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Net\WebSockets\VoiceEvents.cs">
<Link>Net\WebSockets\VoiceEvents.cs</Link>
<Compile Include="..\Discord.Net\WebSockets\Voice\Commands.cs">
<Link>WebSockets\Voice\Commands.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Net\WebSockets\VoiceWebSocket.cs">
<Link>Net\WebSockets\VoiceWebSocket.cs</Link>
<Compile Include="..\Discord.Net\WebSockets\Voice\Events.cs">
<Link>WebSockets\Voice\Events.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Net\WebSockets\VoiceWebSocket.Events.cs">
<Link>Net\WebSockets\VoiceWebSocket.Events.cs</Link>
<Compile Include="..\Discord.Net\WebSockets\Voice\VoiceWebSocket.cs">
<Link>WebSockets\Voice\VoiceWebSocket.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Net\WebSockets\WebSocket.BuiltIn.cs">
<Link>Net\WebSockets\WebSocket.BuiltIn.cs</Link>
<Compile Include="..\Discord.Net\WebSockets\Voice\VoiceWebSocket.Events.cs">
<Link>WebSockets\Voice\VoiceWebSocket.Events.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Net\WebSockets\WebSocket.cs">
<Link>Net\WebSockets\WebSocket.cs</Link>
<Compile Include="..\Discord.Net\WebSockets\WebSocket.BuiltIn.cs">
<Link>WebSockets\WebSocket.BuiltIn.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Net\WebSockets\WebSocket.Events.cs">
<Link>Net\WebSockets\WebSocket.Events.cs</Link>
<Compile Include="..\Discord.Net\WebSockets\WebSocket.cs">
<Link>WebSockets\WebSocket.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Net\WebSockets\WebSocketMessage.cs">
<Link>Net\WebSockets\WebSocketMessage.cs</Link>
<Compile Include="..\Discord.Net\WebSockets\WebSocket.Events.cs">
<Link>WebSockets\WebSocket.Events.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\TimeoutException.cs">
<Link>TimeoutException.cs</Link>
<Compile Include="..\Discord.Net\WebSockets\WebSocketMessage.cs">
<Link>WebSockets\WebSocketMessage.cs</Link>
</Compile> </Compile>
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>


+ 295
- 0
src/Discord.Net/API/Common.cs View File

@@ -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;
}
}

src/Discord.Net/Net/API/Endpoints.cs → src/Discord.Net/API/Endpoints.cs View File

@@ -1,4 +1,4 @@
namespace Discord.Net.API
namespace Discord.API
{ {
internal static class Endpoints internal static class Endpoints
{ {
@@ -45,5 +45,6 @@


public const string StatusActiveMaintenance = "scheduled-maintenances/active.json"; public const string StatusActiveMaintenance = "scheduled-maintenances/active.json";
public const string StatusUnresolvedMaintenance = "scheduled-maintenances/unresolved.json"; public const string StatusUnresolvedMaintenance = "scheduled-maintenances/unresolved.json";
public const string StatusUpcomingMaintenance = "scheduled-maintenances/upcoming.json";
} }
} }

src/Discord.Net/Net/API/HttpException.cs → src/Discord.Net/API/HttpException.cs View File

@@ -1,7 +1,7 @@
using System; using System;
using System.Net; using System.Net;


namespace Discord.Net.API
namespace Discord.API
{ {
public class HttpException : Exception public class HttpException : Exception
{ {

+ 142
- 0
src/Discord.Net/API/Requests.cs View File

@@ -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;
}
}

src/Discord.Net/Net/API/RestClient.BuiltIn.cs → src/Discord.Net/API/RestClient.BuiltIn.cs View File


src/Discord.Net/Net/API/RestClient.Events.cs → src/Discord.Net/API/RestClient.Events.cs View File

@@ -1,7 +1,7 @@
using System; using System;
using System.Net.Http; using System.Net.Http;


namespace Discord.Net.API
namespace Discord.API
{ {
internal partial class RestClient internal partial class RestClient
{ {

src/Discord.Net/Net/API/RestClient.SharpRest.cs → src/Discord.Net/API/RestClient.SharpRest.cs View File

@@ -6,7 +6,7 @@ using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord.Net.API
namespace Discord.API
{ {
internal class RestSharpRestEngine : IRestEngine internal class RestSharpRestEngine : IRestEngine
{ {

src/Discord.Net/Net/API/RestClient.cs → src/Discord.Net/API/RestClient.cs View File

@@ -6,7 +6,7 @@ using System.Reflection;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord.Net.API
namespace Discord.API
{ {
internal interface IRestEngine internal interface IRestEngine
{ {

+ 174
- 325
src/Discord.Net/DiscordClient.API.cs View File

@@ -1,6 +1,5 @@
using Discord.Helpers;
using Discord.Net;
using Discord.Net.API;
using Discord.API;
using Discord.Helpers;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -17,34 +16,54 @@ namespace Discord


public partial class DiscordClient public partial class DiscordClient
{ {
//Servers
/// <summary> Creates a new server with the provided name and region (see Regions). </summary>
public async Task<Server> CreateServer(string name, string region)
public const int MaxMessageSize = 2000;

//Bans
/// <summary> Bans a user from the provided server. </summary>
public Task Ban(Member member)
=> Ban(member?.ServerId, member?.UserId);
/// <summary> Bans a user from the provided server. </summary>
public Task Ban(Server server, User user)
=> Ban(server?.Id, user?.Id);
/// <summary> Bans a user from the provided server. </summary>
public Task Ban(Server server, string userId)
=> Ban(server?.Id, userId);
/// <summary> Bans a user from the provided server. </summary>
public Task Ban(string server, User user)
=> Ban(server, user?.Id);
/// <summary> Bans a user from the provided server. </summary>
public Task Ban(string serverId, string userId)
{ {
CheckReady(); 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);
} }


/// <summary> Leaves the provided server, destroying it if you are the owner. </summary>
public Task<Server> LeaveServer(Server server)
=> LeaveServer(server?.Id);
/// <summary> Leaves the provided server, destroying it if you are the owner. </summary>
public async Task<Server> LeaveServer(string serverId)
/// <summary> Unbans a user from the provided server. </summary>
public Task Unban(Member member)
=> Unban(member?.ServerId, member?.UserId);
/// <summary> Unbans a user from the provided server. </summary>
public Task Unban(Server server, User user)
=> Unban(server?.Id, user?.Id);
/// <summary> Unbans a user from the provided server. </summary>
public Task Unban(Server server, string userId)
=> Unban(server?.Id, userId);
/// <summary> Unbans a user from the provided server. </summary>
public Task Unban(string server, User user)
=> Unban(server, user?.Id);
/// <summary> Unbans a user from the provided server. </summary>
public async Task Unban(string serverId, string userId)
{ {
CheckReady(); CheckReady();
if (serverId == null) throw new ArgumentNullException(nameof(serverId)); 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) { } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
return _servers.TryRemove(serverId);
} }

//Channels //Channels
/// <summary> Creates a new channel with the provided name and type (see ChannelTypes). </summary> /// <summary> Creates a new channel with the provided name and type (see ChannelTypes). </summary>
public Task<Channel> CreateChannel(Server server, string name, string type) public Task<Channel> CreateChannel(Server server, string name, string type)
@@ -62,6 +81,7 @@ namespace Discord
channel.Update(response); channel.Update(response);
return channel; return channel;
} }

/// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary> /// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary>
public Task<Channel> CreatePMChannel(string userId) => CreatePMChannel(_users[userId], userId); public Task<Channel> CreatePMChannel(string userId) => CreatePMChannel(_users[userId], userId);
/// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary> /// <summary> Returns the private channel with the provided user, creating one if it does not currently exist. </summary>
@@ -70,7 +90,7 @@ namespace Discord
public Task<Channel> CreatePMChannel(Member member) => CreatePMChannel(member.User, member.UserId); public Task<Channel> CreatePMChannel(Member member) => CreatePMChannel(member.User, member.UserId);
private async Task<Channel> CreatePMChannel(User user, string userId) private async Task<Channel> CreatePMChannel(User user, string userId)
{ {
CheckReady();
CheckReady();
if (userId == null) throw new ArgumentNullException(nameof(userId)); if (userId == null) throw new ArgumentNullException(nameof(userId));


Channel channel = null; Channel channel = null;
@@ -85,6 +105,19 @@ namespace Discord
return channel; return channel;
} }


/// <summary> Edits the provided channel, changing only non-null attributes. </summary>
public Task EditChannel(Channel channel)
=> EditChannel(channel?.Id);
/// <summary> Edits the provided channel, changing only non-null attributes. </summary>
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);
}

/// <summary> Destroys the provided channel. </summary> /// <summary> Destroys the provided channel. </summary>
public Task<Channel> DestroyChannel(Channel channel) public Task<Channel> DestroyChannel(Channel channel)
=> DestroyChannel(channel?.Id); => DestroyChannel(channel?.Id);
@@ -99,52 +132,6 @@ namespace Discord
return _channels.TryRemove(channelId); return _channels.TryRemove(channelId);
} }


//Bans
/// <summary> Bans a user from the provided server. </summary>
public Task Ban(Member member)
=> Ban(member?.ServerId, member?.UserId);
/// <summary> Bans a user from the provided server. </summary>
public Task Ban(Server server, User user)
=> Ban(server?.Id, user?.Id);
/// <summary> Bans a user from the provided server. </summary>
public Task Ban(Server server, string userId)
=> Ban(server?.Id, userId);
/// <summary> Bans a user from the provided server. </summary>
public Task Ban(string server, User user)
=> Ban(server, user?.Id);
/// <summary> Bans a user from the provided server. </summary>
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);
}

/// <summary> Unbans a user from the provided server. </summary>
public Task Unban(Member member)
=> Unban(member?.ServerId, member?.UserId);
/// <summary> Unbans a user from the provided server. </summary>
public Task Unban(Server server, User user)
=> Unban(server?.Id, user?.Id);
/// <summary> Unbans a user from the provided server. </summary>
public Task Unban(Server server, string userId)
=> Unban(server?.Id, userId);
/// <summary> Unbans a user from the provided server. </summary>
public Task Unban(string server, User user)
=> Unban(server, user?.Id);
/// <summary> Unbans a user from the provided server. </summary>
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 //Invites
/// <summary> Creates a new invite to the default channel of the provided server. </summary> /// <summary> Creates a new invite to the default channel of the provided server. </summary>
/// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param> /// <param name="maxAge"> Time (in seconds) until the invite expires. Set to 0 to never expire. </param>
@@ -178,14 +165,29 @@ namespace Discord
return invite; return invite;
} }


/// <summary> Deletes the provided invite. </summary>
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) { }
}

/// <summary> Gets more info about the provided invite code. </summary> /// <summary> Gets more info about the provided invite code. </summary>
/// <remarks> Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode </remarks> /// <remarks> Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode </remarks>
public async Task<Invite> GetInvite(string id)
public async Task<Invite> GetInvite(string inviteIdOrXkcd)
{ {
CheckReady(); 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); var invite = new Invite(this, response.Code, response.XkcdPass, response.Guild.Id);
invite.Update(response); invite.Update(response);
return invite; return invite;
@@ -200,39 +202,42 @@ namespace Discord
return _api.AcceptInvite(invite.Id); return _api.AcceptInvite(invite.Id);
} }
/// <summary> Accepts the provided invite. </summary> /// <summary> Accepts the provided invite. </summary>
public async Task AcceptInvite(string code)
public async Task AcceptInvite(string inviteId)
{ {
CheckReady(); 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 //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) 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 //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); await _api.AcceptInvite(invite.Id).ConfigureAwait(false);
} }


/// <summary> Deletes the provided invite. </summary>
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(); 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
/// <summary> Sends a message to the provided channel. </summary> /// <summary> Sends a message to the provided channel. </summary>
public Task<Message[]> SendMessage(Channel channel, string text) public Task<Message[]> SendMessage(Channel channel, string text)
=> SendMessage(channel?.Id, text, new string[0]); => SendMessage(channel?.Id, text, new string[0]);
@@ -252,18 +257,18 @@ namespace Discord
if (text == null) throw new ArgumentNullException(nameof(text)); if (text == null) throw new ArgumentNullException(nameof(text));
if (mentions == null) throw new ArgumentNullException(nameof(mentions)); 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]; Message[] result = new Message[blockCount];
for (int i = 0; i < blockCount; i++) 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)); string blockText = text.Substring(index, Math.Min(2000, text.Length - index));
var nonce = GenerateNonce(); var nonce = GenerateNonce();
if (_config.UseMessageQueue) if (_config.UseMessageQueue)
{ {
var msg = _messages.GetOrAdd("nonce_" + nonce, channel.Id, _currentUserId); var msg = _messages.GetOrAdd("nonce_" + nonce, channel.Id, _currentUserId);
var currentMember = _members[msg.UserId, channel.ServerId]; var currentMember = _members[msg.UserId, channel.ServerId];
msg.Update(new Net.API.Message
msg.Update(new API.Message
{ {
Content = blockText, Content = blockText,
Timestamp = DateTime.UtcNow, Timestamp = DateTime.UtcNow,
@@ -302,35 +307,37 @@ namespace Discord
return await SendMessage(channel, text, new string[0]).ConfigureAwait(false); return await SendMessage(channel, text, new string[0]).ConfigureAwait(false);
} }


/// <summary> Edits a message the provided message. </summary>
public Task EditMessage(Message message, string text)
=> EditMessage(message?.ChannelId, message?.Id, text, new string[0]);
/// <summary> Edits a message the provided message. </summary>
public Task EditMessage(Channel channel, string messageId, string text)
=> EditMessage(channel?.Id, messageId, text, new string[0]);
/// <summary> Edits a message the provided message. </summary>
public Task EditMessage(string channelId, string messageId, string text)
=> EditMessage(channelId, messageId, text, new string[0]);
/// <summary> Edits a message the provided message, mentioning certain users. </summary>
/// <summary> Sends a file to the provided channel. </summary>
public Task SendFile(Channel channel, string filePath)
=> SendFile(channel?.Id, filePath);
/// <summary> Sends a file to the provided channel. </summary>
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);
}

/// <summary> Edits the provided message, changing only non-null attributes. </summary>
/// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks> /// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks>
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); => EditMessage(message?.ChannelId, message?.Id, text, mentions);
/// <summary> Edits a message the provided message, mentioning certain users. </summary>
/// <summary> Edits the provided message, changing only non-null attributes. </summary>
/// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks> /// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks>
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); => EditMessage(channel?.Id, messageId, text, mentions);
/// <summary> Edits a message the provided message, mentioning certain users. </summary>
/// <summary> Edits the provided message, changing only non-null attributes. </summary>
/// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks> /// <remarks> While not required, it is recommended to include a mention reference in the text (see Mention.User). </remarks>
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(); CheckReady();
if (channelId == null) throw new ArgumentNullException(nameof(channelId)); if (channelId == null) throw new ArgumentNullException(nameof(channelId));
if (messageId == null) throw new ArgumentNullException(nameof(messageId)); 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 model = await _api.EditMessage(messageId, channelId, text, mentions).ConfigureAwait(false);
var msg = _messages[messageId]; var msg = _messages[messageId];
@@ -383,19 +390,6 @@ namespace Discord
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
} }
} }
/// <summary> Sends a file to the provided channel. </summary>
public Task SendFile(Channel channel, string filePath)
=> SendFile(channel?.Id, filePath);
/// <summary> Sends a file to the provided channel. </summary>
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);
}


/// <summary> Downloads last count messages from the server, starting at beforeMessageId if it's provided. </summary> /// <summary> Downloads last count messages from the server, starting at beforeMessageId if it's provided. </summary>
public Task<Message[]> DownloadMessages(Channel channel, int count, string beforeMessageId = null, bool cache = true) public Task<Message[]> DownloadMessages(Channel channel, int count, string beforeMessageId = null, bool cache = true)
@@ -449,113 +443,6 @@ namespace Discord
return null; return null;
} }


//Roles
/// <summary>Note: due to current API limitations, the created role cannot be returned. </summary>
public Task CreateRole(Server server)
=> CreateRole(server?.Id);
/// <summary>Note: due to current API limitations, the created role cannot be returned. </summary>
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 //Permissions
public Task SetChannelUserPermissions(Channel channel, Member member, PackedPermissions allow, PackedPermissions deny) public Task SetChannelUserPermissions(Channel channel, Member member, PackedPermissions allow, PackedPermissions deny)
=> SetChannelPermissions(channel?.Id, member?.UserId, "member", allow, deny); => SetChannelPermissions(channel?.Id, member?.UserId, "member", allow, deny);
@@ -585,7 +472,7 @@ namespace Discord
if (channelId == null) throw new NullReferenceException(nameof(channelId)); if (channelId == null) throw new NullReferenceException(nameof(channelId));
if (userOrRoleId == null) throw new NullReferenceException(nameof(userOrRoleId)); 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 //TODO: Remove permission from cache
} }


@@ -625,128 +512,90 @@ namespace Discord
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { }
} }


//Voice
/// <summary> Mutes a user on the provided server. </summary>
public Task Mute(Member member)
=> Mute(member?.ServerId, member?.UserId);
/// <summary> Mutes a user on the provided server. </summary>
public Task Mute(Server server, User user)
=> Mute(server?.Id, user?.Id);
/// <summary> Mutes a user on the provided server. </summary>
public Task Mute(Server server, string userId)
=> Mute(server?.Id, userId);
/// <summary> Mutes a user on the provided server. </summary>
public Task Mute(string server, User user)
=> Mute(server, user?.Id);
/// <summary> Mutes a user on the provided server. </summary>
public Task Mute(string serverId, string userId)
//Profile
public Task<EditProfileResponse> 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);
} }


/// <summary> Mutes a user on the provided server. </summary>
public Task Unmute(Member member)
=> Unmute(member?.ServerId, member?.UserId);
/// <summary> Unmutes a user on the provided server. </summary>
public Task Unmute(Server server, User user)
=> Unmute(server?.Id, user?.Id);
/// <summary> Unmutes a user on the provided server. </summary>
public Task Unmute(Server server, string userId)
=> Unmute(server?.Id, userId);
/// <summary> Unmutes a user on the provided server. </summary>
public Task Unmute(string server, User user)
=> Unmute(server, user?.Id);
/// <summary> Unmutes a user on the provided server. </summary>
public Task Unmute(string serverId, string userId)
//Roles
/// <summary> Note: due to current API limitations, the created role cannot be returned. </summary>
public Task CreateRole(Server server)
=> CreateRole(server?.Id);
/// <summary> Note: due to current API limitations, the created role cannot be returned. </summary>
public Task CreateRole(string serverId)
{ {
CheckReady(); 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);
} }


/// <summary> Deafens a user on the provided server. </summary>
public Task Deafen(Member member)
=> Deafen(member?.ServerId, member?.UserId);
/// <summary> Deafens a user on the provided server. </summary>
public Task Deafen(Server server, User user)
=> Deafen(server?.Id, user?.Id);
/// <summary> Deafens a user on the provided server. </summary>
public Task Deafen(Server server, string userId)
=> Deafen(server?.Id, userId);
/// <summary> Deafens a user on the provided server. </summary>
public Task Deafen(string server, User user)
=> Deafen(server, user?.Id);
/// <summary> Deafens a user on the provided server. </summary>
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(); 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);
} }


/// <summary> Undeafens a user on the provided server. </summary>
public Task Undeafen(Member member)
=> Undeafen(member?.ServerId, member?.UserId);
/// <summary> Undeafens a user on the provided server. </summary>
public Task Undeafen(Server server, User user)
=> Undeafen(server?.Id, user?.Id);
/// <summary> Undeafens a user on the provided server. </summary>
public Task Undeafen(Server server, string userId)
=> Undeafen(server?.Id, userId);
/// <summary> Undeafens a user on the provided server. </summary>
public Task Undeafen(string server, User user)
=> Undeafen(server, user?.Id);
/// <summary> Undeafens a user on the provided server. </summary>
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(); 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
/// <summary> Changes your username to newName. </summary>
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);
}
/// <summary> Changes your email to newEmail. </summary>
public async Task ChangeEmail(string newEmail, string currentPassword)
//Servers
/// <summary> Creates a new server with the provided name and region (see Regions). </summary>
public async Task<Server> CreateServer(string name, string region)
{ {
CheckReady(); 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;
} }
/// <summary> Changes your password to newPassword. </summary>
public async Task ChangePassword(string newPassword, string currentEmail, string currentPassword)

/// <summary> Edits the provided server, changing only non-null attributes. </summary>
public Task EditServer(Server server)
=> EditServer(server?.Id);
/// <summary> Edits the provided server, changing only non-null attributes. </summary>
public Task EditServer(string serverId, string name = null, string region = null)
{ {
CheckReady(); 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);
} }


/// <summary> Changes your avatar. </summary>
/// <remarks>Only supports PNG and JPEG (see AvatarImageType)</remarks>
public async Task ChangeAvatar(AvatarImageType imageType, byte[] bytes, string currentEmail, string currentPassword)
/// <summary> Leaves the provided server, destroying it if you are the owner. </summary>
public Task<Server> LeaveServer(Server server)
=> LeaveServer(server?.Id);
/// <summary> Leaves the provided server, destroying it if you are the owner. </summary>
public async Task<Server> LeaveServer(string serverId)
{ {
CheckReady(); 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);
} }
} }
} }

+ 2
- 1
src/Discord.Net/DiscordClient.Voice.cs View File

@@ -1,4 +1,5 @@
using Discord.Helpers; using Discord.Helpers;
using Discord.WebSockets;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;


@@ -36,7 +37,7 @@ namespace Discord
{ {
CheckReady(checkVoice: true); CheckReady(checkVoice: true);


if (_voiceSocket.State != Net.WebSockets.WebSocketState.Disconnected)
if (_voiceSocket.State != WebSocketState.Disconnected)
{ {
var serverId = _voiceSocket.CurrentVoiceServerId; var serverId = _voiceSocket.CurrentVoiceServerId;
if (serverId != null) if (serverId != null)


+ 32
- 31
src/Discord.Net/DiscordClient.cs View File

@@ -1,8 +1,8 @@
using Discord.Collections;
using Discord.API;
using Discord.Collections;
using Discord.Helpers; 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 Newtonsoft.Json;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@@ -10,6 +10,7 @@ using System.Net;
using System.Runtime.ExceptionServices; using System.Runtime.ExceptionServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using VoiceWebSocket = Discord.WebSockets.Voice.VoiceWebSocket;


namespace Discord namespace Discord
{ {
@@ -279,7 +280,7 @@ namespace Discord
//Global //Global
case "READY": //Resync case "READY": //Resync
{ {
var data = e.Payload.ToObject<Events.Ready>(_serializer);
var data = e.Payload.ToObject<ReadyEvent>(_serializer);
_currentUserId = data.User.Id; _currentUserId = data.User.Id;
_currentUser = _users.GetOrAdd(data.User.Id); _currentUser = _users.GetOrAdd(data.User.Id);
_currentUser.Update(data.User); _currentUser.Update(data.User);
@@ -303,7 +304,7 @@ namespace Discord
//Servers //Servers
case "GUILD_CREATE": case "GUILD_CREATE":
{ {
var model = e.Payload.ToObject<Events.GuildCreate>(_serializer);
var model = e.Payload.ToObject<GuildCreateEvent>(_serializer);
var server = _servers.GetOrAdd(model.Id); var server = _servers.GetOrAdd(model.Id);
server.Update(model); server.Update(model);
RaiseServerCreated(server); RaiseServerCreated(server);
@@ -311,7 +312,7 @@ namespace Discord
break; break;
case "GUILD_UPDATE": case "GUILD_UPDATE":
{ {
var model = e.Payload.ToObject<Events.GuildUpdate>(_serializer);
var model = e.Payload.ToObject<GuildUpdateEvent>(_serializer);
var server = _servers[model.Id]; var server = _servers[model.Id];
if (server != null) if (server != null)
{ {
@@ -322,7 +323,7 @@ namespace Discord
break; break;
case "GUILD_DELETE": case "GUILD_DELETE":
{ {
var data = e.Payload.ToObject<Events.GuildDelete>(_serializer);
var data = e.Payload.ToObject<GuildDeleteEvent>(_serializer);
var server = _servers.TryRemove(data.Id); var server = _servers.TryRemove(data.Id);
if (server != null) if (server != null)
RaiseServerDestroyed(server); RaiseServerDestroyed(server);
@@ -332,7 +333,7 @@ namespace Discord
//Channels //Channels
case "CHANNEL_CREATE": case "CHANNEL_CREATE":
{ {
var data = e.Payload.ToObject<Events.ChannelCreate>(_serializer);
var data = e.Payload.ToObject<ChannelCreateEvent>(_serializer);
Channel channel; Channel channel;
if (data.IsPrivate) if (data.IsPrivate)
{ {
@@ -348,7 +349,7 @@ namespace Discord
break; break;
case "CHANNEL_UPDATE": case "CHANNEL_UPDATE":
{ {
var data = e.Payload.ToObject<Events.ChannelUpdate>(_serializer);
var data = e.Payload.ToObject<ChannelUpdateEvent>(_serializer);
var channel = _channels[data.Id]; var channel = _channels[data.Id];
if (channel != null) if (channel != null)
{ {
@@ -359,7 +360,7 @@ namespace Discord
break; break;
case "CHANNEL_DELETE": case "CHANNEL_DELETE":
{ {
var data = e.Payload.ToObject<Events.ChannelDelete>(_serializer);
var data = e.Payload.ToObject<ChannelDeleteEvent>(_serializer);
var channel = _channels.TryRemove(data.Id); var channel = _channels.TryRemove(data.Id);
if (channel != null) if (channel != null)
RaiseChannelDestroyed(channel); RaiseChannelDestroyed(channel);
@@ -369,7 +370,7 @@ namespace Discord
//Members //Members
case "GUILD_MEMBER_ADD": case "GUILD_MEMBER_ADD":
{ {
var data = e.Payload.ToObject<Events.GuildMemberAdd>(_serializer);
var data = e.Payload.ToObject<GuildMemberAddEvent>(_serializer);
var user = _users.GetOrAdd(data.User.Id); var user = _users.GetOrAdd(data.User.Id);
var member = _members.GetOrAdd(data.User.Id, data.GuildId); var member = _members.GetOrAdd(data.User.Id, data.GuildId);
user.Update(data.User); user.Update(data.User);
@@ -381,7 +382,7 @@ namespace Discord
break; break;
case "GUILD_MEMBER_UPDATE": case "GUILD_MEMBER_UPDATE":
{ {
var data = e.Payload.ToObject<Events.GuildMemberUpdate>(_serializer);
var data = e.Payload.ToObject<GuildMemberUpdateEvent>(_serializer);
var member = _members[data.User.Id, data.GuildId]; var member = _members[data.User.Id, data.GuildId];
if (member != null) if (member != null)
{ {
@@ -392,7 +393,7 @@ namespace Discord
break; break;
case "GUILD_MEMBER_REMOVE": case "GUILD_MEMBER_REMOVE":
{ {
var data = e.Payload.ToObject<Events.GuildMemberRemove>(_serializer);
var data = e.Payload.ToObject<GuildMemberRemoveEvent>(_serializer);
var member = _members.TryRemove(data.UserId, data.GuildId); var member = _members.TryRemove(data.UserId, data.GuildId);
if (member != null) if (member != null)
RaiseUserRemoved(member); RaiseUserRemoved(member);
@@ -402,7 +403,7 @@ namespace Discord
//Roles //Roles
case "GUILD_ROLE_CREATE": case "GUILD_ROLE_CREATE":
{ {
var data = e.Payload.ToObject<Events.GuildRoleCreate>(_serializer);
var data = e.Payload.ToObject<GuildRoleCreateEvent>(_serializer);
var role = _roles.GetOrAdd(data.Data.Id, data.GuildId); var role = _roles.GetOrAdd(data.Data.Id, data.GuildId);
role.Update(data.Data); role.Update(data.Data);
RaiseRoleUpdated(role); RaiseRoleUpdated(role);
@@ -410,7 +411,7 @@ namespace Discord
break; break;
case "GUILD_ROLE_UPDATE": case "GUILD_ROLE_UPDATE":
{ {
var data = e.Payload.ToObject<Events.GuildRoleUpdate>(_serializer);
var data = e.Payload.ToObject<GuildRoleUpdateEvent>(_serializer);
var role = _roles[data.Data.Id]; var role = _roles[data.Data.Id];
if (role != null) if (role != null)
role.Update(data.Data); role.Update(data.Data);
@@ -419,7 +420,7 @@ namespace Discord
break; break;
case "GUILD_ROLE_DELETE": case "GUILD_ROLE_DELETE":
{ {
var data = e.Payload.ToObject<Events.GuildRoleDelete>(_serializer);
var data = e.Payload.ToObject<GuildRoleDeleteEvent>(_serializer);
var role = _roles.TryRemove(data.RoleId); var role = _roles.TryRemove(data.RoleId);
if (role != null) if (role != null)
RaiseRoleDeleted(role); RaiseRoleDeleted(role);
@@ -429,7 +430,7 @@ namespace Discord
//Bans //Bans
case "GUILD_BAN_ADD": case "GUILD_BAN_ADD":
{ {
var data = e.Payload.ToObject<Events.GuildBanAdd>(_serializer);
var data = e.Payload.ToObject<GuildBanAddEvent>(_serializer);
var server = _servers[data.GuildId]; var server = _servers[data.GuildId];
if (server != null) if (server != null)
{ {
@@ -440,7 +441,7 @@ namespace Discord
break; break;
case "GUILD_BAN_REMOVE": case "GUILD_BAN_REMOVE":
{ {
var data = e.Payload.ToObject<Events.GuildBanRemove>(_serializer);
var data = e.Payload.ToObject<GuildBanRemoveEvent>(_serializer);
var server = _servers[data.GuildId]; var server = _servers[data.GuildId];
if (server != null && server.RemoveBan(data.UserId)) if (server != null && server.RemoveBan(data.UserId))
RaiseBanRemoved(data.UserId, server); RaiseBanRemoved(data.UserId, server);
@@ -450,7 +451,7 @@ namespace Discord
//Messages //Messages
case "MESSAGE_CREATE": case "MESSAGE_CREATE":
{ {
var data = e.Payload.ToObject<Events.MessageCreate>(_serializer);
var data = e.Payload.ToObject<MessageCreateEvent>(_serializer);
Message msg = null; Message msg = null;


bool wasLocal = _config.UseMessageQueue && data.Author.Id == _currentUserId && data.Nonce != null; bool wasLocal = _config.UseMessageQueue && data.Author.Id == _currentUserId && data.Nonce != null;
@@ -490,7 +491,7 @@ namespace Discord
break; break;
case "MESSAGE_UPDATE": case "MESSAGE_UPDATE":
{ {
var data = e.Payload.ToObject<Events.MessageUpdate>(_serializer);
var data = e.Payload.ToObject<MessageUpdateEvent>(_serializer);
var msg = _messages[data.Id]; var msg = _messages[data.Id];
if (msg != null) if (msg != null)
{ {
@@ -501,7 +502,7 @@ namespace Discord
break; break;
case "MESSAGE_DELETE": case "MESSAGE_DELETE":
{ {
var data = e.Payload.ToObject<Events.MessageDelete>(_serializer);
var data = e.Payload.ToObject<MessageDeleteEvent>(_serializer);
var msg = _messages.TryRemove(data.Id); var msg = _messages.TryRemove(data.Id);
if (msg != null) if (msg != null)
RaiseMessageDeleted(msg); RaiseMessageDeleted(msg);
@@ -509,7 +510,7 @@ namespace Discord
break; break;
case "MESSAGE_ACK": case "MESSAGE_ACK":
{ {
var data = e.Payload.ToObject<Events.MessageAck>(_serializer);
var data = e.Payload.ToObject<MessageAckEvent>(_serializer);
var msg = GetMessage(data.MessageId); var msg = GetMessage(data.MessageId);
if (msg != null) if (msg != null)
RaiseMessageReadRemotely(msg); RaiseMessageReadRemotely(msg);
@@ -519,7 +520,7 @@ namespace Discord
//Statuses //Statuses
case "PRESENCE_UPDATE": case "PRESENCE_UPDATE":
{ {
var data = e.Payload.ToObject<Events.PresenceUpdate>(_serializer);
var data = e.Payload.ToObject<PresenceUpdateEvent>(_serializer);
var member = _members[data.User.Id, data.GuildId]; var member = _members[data.User.Id, data.GuildId];
/*if (_config.TrackActivity) /*if (_config.TrackActivity)
{ {
@@ -536,7 +537,7 @@ namespace Discord
break; break;
case "VOICE_STATE_UPDATE": case "VOICE_STATE_UPDATE":
{ {
var data = e.Payload.ToObject<Events.VoiceStateUpdate>(_serializer);
var data = e.Payload.ToObject<VoiceStateUpdateEvent>(_serializer);
var member = _members[data.UserId, data.GuildId]; var member = _members[data.UserId, data.GuildId];
/*if (_config.TrackActivity) /*if (_config.TrackActivity)
{ {
@@ -558,7 +559,7 @@ namespace Discord
break; break;
case "TYPING_START": case "TYPING_START":
{ {
var data = e.Payload.ToObject<Events.TypingStart>(_serializer);
var data = e.Payload.ToObject<TypingStartEvent>(_serializer);
var channel = _channels[data.ChannelId]; var channel = _channels[data.ChannelId];
var user = _users[data.UserId]; var user = _users[data.UserId];


@@ -587,7 +588,7 @@ namespace Discord
//Voice //Voice
case "VOICE_SERVER_UPDATE": case "VOICE_SERVER_UPDATE":
{ {
var data = e.Payload.ToObject<Events.VoiceServerUpdate>(_serializer);
var data = e.Payload.ToObject<VoiceServerUpdateEvent>(_serializer);
if (data.GuildId == _voiceSocket.CurrentVoiceServerId) if (data.GuildId == _voiceSocket.CurrentVoiceServerId)
{ {
var server = _servers[data.GuildId]; var server = _servers[data.GuildId];
@@ -603,7 +604,7 @@ namespace Discord
//Settings //Settings
case "USER_UPDATE": case "USER_UPDATE":
{ {
var data = e.Payload.ToObject<Events.UserUpdate>(_serializer);
var data = e.Payload.ToObject<UserUpdateEvent>(_serializer);
var user = _users[data.Id]; var user = _users[data.Id];
if (user != null) if (user != null)
{ {
@@ -670,8 +671,8 @@ namespace Discord
_token = token; _token = token;
_state = (int)DiscordClientState.Connecting; _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}"); RaiseOnLog(LogMessageSeverity.Verbose, LogMessageSource.Client, $"Websocket endpoint: {url}");


_dataSocket.Host = url; _dataSocket.Host = url;
@@ -838,7 +839,7 @@ namespace Discord
while (_pendingMessages.TryDequeue(out msg)) while (_pendingMessages.TryDequeue(out msg))
{ {
bool hasFailed = false; bool hasFailed = false;
Responses.SendMessage response = null;
SendMessageResponse response = null;
try try
{ {
response = await _api.SendMessage(msg.ChannelId, msg.RawText, msg.MentionIds, msg.Nonce, msg.IsTTS).ConfigureAwait(false); response = await _api.SendMessage(msg.ChannelId, msg.RawText, msg.MentionIds, msg.Nonce, msg.IsTTS).ConfigureAwait(false);


+ 2
- 3
src/Discord.Net/Format.cs View File

@@ -1,5 +1,4 @@
using Discord.Net.API;
using System.Text;
using System.Text;


namespace Discord namespace Discord
{ {
@@ -11,7 +10,7 @@ namespace Discord
static Format() static Format()
{ {
_patterns = new string[] { "__", "_", "**", "*", "~~" }; _patterns = new string[] { "__", "_", "**", "*", "~~" };
_builder = new StringBuilder(DiscordAPIClient.MaxMessageSize);
_builder = new StringBuilder(DiscordClient.MaxMessageSize);
} }


/// <summary> Removes all special formatting characters from the provided text. </summary> /// <summary> Removes all special formatting characters from the provided text. </summary>


+ 1
- 6
src/Discord.Net/Mention.cs View File

@@ -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 public static class Mention
{ {


+ 4
- 5
src/Discord.Net/Models/Channel.cs View File

@@ -1,5 +1,4 @@
using Discord.Net.API;
using Newtonsoft.Json;
using Newtonsoft.Json;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -64,14 +63,14 @@ namespace Discord
_messages = new ConcurrentDictionary<string, bool>(); _messages = new ConcurrentDictionary<string, bool>();
} }


internal void Update(ChannelReference model)
internal void Update(API.ChannelReference model)
{ {
Name = model.Name; Name = model.Name;
Type = model.Type; 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; Position = model.Position;




+ 5
- 6
src/Discord.Net/Models/Invite.cs View File

@@ -1,5 +1,4 @@
using Discord.Net.API;
using Newtonsoft.Json;
using Newtonsoft.Json;


namespace Discord namespace Discord
{ {
@@ -24,7 +23,7 @@ namespace Discord
public string XkcdPass { get; } public string XkcdPass { get; }


/// <summary> Returns a URL for this invite using XkcdPass if available or Id if not. </summary> /// <summary> Returns a URL for this invite using XkcdPass if available or Id if not. </summary>
public string Url => Endpoints.InviteUrl(XkcdPass ?? Id);
public string Url => API.Endpoints.InviteUrl(XkcdPass ?? Id);


/// <summary> Returns the id of the user that created this invite. </summary> /// <summary> Returns the id of the user that created this invite. </summary>
public string InviterId { get; internal set; } public string InviterId { get; internal set; }
@@ -54,16 +53,16 @@ namespace Discord


public override string ToString() => XkcdPass ?? Id; public override string ToString() => XkcdPass ?? Id;


internal void Update(Net.API.Invite model)
internal void Update(API.Invite model)
{ {
ChannelId = model.Channel.Id; ChannelId = model.Channel.Id;
InviterId = model.Inviter?.Id; InviterId = model.Inviter?.Id;
ServerId = model.Guild.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; IsRevoked = model.IsRevoked;
IsTemporary = model.IsTemporary; IsTemporary = model.IsTemporary;
MaxAge = model.MaxAge; MaxAge = model.MaxAge;


+ 8
- 9
src/Discord.Net/Models/Member.cs View File

@@ -1,5 +1,4 @@
using Discord.Net.API;
using Newtonsoft.Json;
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -17,7 +16,7 @@ namespace Discord
/// <summary> Returns the unique identifier for this user's current avatar. </summary> /// <summary> Returns the unique identifier for this user's current avatar. </summary>
public string AvatarId { get; internal set; } public string AvatarId { get; internal set; }
/// <summary> Returns the URL to this user's current avatar. </summary> /// <summary> Returns the URL to this user's current avatar. </summary>
public string AvatarUrl => Endpoints.UserAvatar(UserId, AvatarId);
public string AvatarUrl => API.Endpoints.UserAvatar(UserId, AvatarId);
/// <summary> Returns the datetime that this user joined this server. </summary> /// <summary> Returns the datetime that this user joined this server. </summary>
public DateTime JoinedAt { get; internal set; } public DateTime JoinedAt { get; internal set; }


@@ -70,7 +69,7 @@ namespace Discord


public override string ToString() => UserId; public override string ToString() => UserId;


internal void Update(UserReference model)
internal void Update(API.UserReference model)
{ {
if (model.Avatar != null) if (model.Avatar != null)
AvatarId = model.Avatar; AvatarId = model.Avatar;
@@ -79,7 +78,7 @@ namespace Discord
if (model.Username != null) if (model.Username != null)
Name = model.Username; Name = model.Username;
} }
internal void Update(MemberInfo model)
internal void Update(API.MemberInfo model)
{ {
if (model.User != null) if (model.User != null)
Update(model.User); Update(model.User);
@@ -87,13 +86,13 @@ namespace Discord
if (model.JoinedAt.HasValue) if (model.JoinedAt.HasValue)
JoinedAt = model.JoinedAt.Value; 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; IsDeafened = model.IsDeafened;
IsMuted = model.IsMuted; IsMuted = model.IsMuted;
} }
internal void Update(PresenceMemberInfo model)
internal void Update(API.PresenceMemberInfo model)
{ {
if (Status != model.Status) if (Status != model.Status)
{ {
@@ -103,7 +102,7 @@ namespace Discord
} }
GameId = model.GameId; GameId = model.GameId;
} }
internal void Update(VoiceMemberInfo model)
internal void Update(API.VoiceMemberInfo model)
{ {
IsDeafened = model.IsDeafened; IsDeafened = model.IsDeafened;
IsMuted = model.IsMuted; IsMuted = model.IsMuted;


+ 1
- 1
src/Discord.Net/Models/Message.cs View File

@@ -122,7 +122,7 @@ namespace Discord
UserId = userId; UserId = userId;
} }


internal void Update(Net.API.Message model)
internal void Update(API.Message model)
{ {
if (model.Attachments != null) if (model.Attachments != null)
{ {


+ 1
- 2
src/Discord.Net/Models/PackedPermissions.cs View File

@@ -71,8 +71,7 @@ namespace Discord
else else
_rawValue &= ~(1U << (pos - 1)); _rawValue &= ~(1U << (pos - 1));
} }

public static implicit operator uint (PackedPermissions perms) => perms._rawValue;
public PackedPermissions Copy() => new PackedPermissions(false, _rawValue); public PackedPermissions Copy() => new PackedPermissions(false, _rawValue);
} }
} }

+ 1
- 2
src/Discord.Net/Models/Role.cs View File

@@ -1,5 +1,4 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using System.Threading;


namespace Discord namespace Discord
{ {
@@ -29,7 +28,7 @@ namespace Discord
Permissions = new PackedPermissions(true); Permissions = new PackedPermissions(true);
} }


internal void Update(Net.API.RoleInfo model)
internal void Update(API.RoleInfo model)
{ {
Name = model.Name; Name = model.Name;
Permissions.RawValue = (uint)model.Permissions; Permissions.RawValue = (uint)model.Permissions;


+ 4
- 5
src/Discord.Net/Models/Server.cs View File

@@ -1,5 +1,4 @@
using Discord.Net.API;
using Newtonsoft.Json;
using Newtonsoft.Json;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@@ -100,7 +99,7 @@ namespace Discord
_roles = new ConcurrentDictionary<string, bool>(); _roles = new ConcurrentDictionary<string, bool>();
} }


internal void Update(GuildInfo model)
internal void Update(API.GuildInfo model)
{ {
AFKChannelId = model.AFKChannelId; AFKChannelId = model.AFKChannelId;
AFKTimeout = model.AFKTimeout; AFKTimeout = model.AFKTimeout;
@@ -117,9 +116,9 @@ namespace Discord
role.Update(subModel); 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; var channels = _client.Channels;
foreach (var subModel in model.Channels) foreach (var subModel in model.Channels)


+ 5
- 6
src/Discord.Net/Models/User.cs View File

@@ -1,5 +1,4 @@
using Discord.Net.API;
using Newtonsoft.Json;
using Newtonsoft.Json;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@@ -24,7 +23,7 @@ namespace Discord
/// <summary> Returns the unique identifier for this user's current avatar. </summary> /// <summary> Returns the unique identifier for this user's current avatar. </summary>
public string AvatarId { get; internal set; } public string AvatarId { get; internal set; }
/// <summary> Returns the URL to this user's current avatar. </summary> /// <summary> Returns the URL to this user's current avatar. </summary>
public string AvatarUrl => Endpoints.UserAvatar(Id, AvatarId);
public string AvatarUrl => API.Endpoints.UserAvatar(Id, AvatarId);


/// <summary> Returns the email for this user. </summary> /// <summary> Returns the email for this user. </summary>
/// <remarks> This field is only ever populated for the current logged in user. </remarks> /// <remarks> This field is only ever populated for the current logged in user. </remarks>
@@ -78,7 +77,7 @@ namespace Discord
_servers = new ConcurrentDictionary<string, bool>(); _servers = new ConcurrentDictionary<string, bool>();
} }


internal void Update(UserReference model)
internal void Update(API.UserReference model)
{ {
if (model.Avatar != null) if (model.Avatar != null)
AvatarId = model.Avatar; AvatarId = model.Avatar;
@@ -87,9 +86,9 @@ namespace Discord
if (model.Username != null) if (model.Username != null)
Name = model.Username; 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; Email = model.Email;
IsVerified = model.IsVerified; IsVerified = model.IsVerified;
} }


+ 0
- 295
src/Discord.Net/Net/API/Common.cs View File

@@ -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;
}
}

+ 0
- 212
src/Discord.Net/Net/API/DiscordAPIClient.cs View File

@@ -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<Responses.Gateway> GetWebSocketEndpoint()
=> _rest.Get<Responses.Gateway>(Endpoints.Gateway);
public async Task<Responses.AuthRegister> LoginAnonymous(string username)
{
var fingerprintResponse = await _rest.Post<Responses.AuthFingerprint>(Endpoints.AuthFingerprint).ConfigureAwait(false);
var registerRequest = new Requests.AuthRegister { Fingerprint = fingerprintResponse.Fingerprint, Username = username };
var registerResponse = await _rest.Post<Responses.AuthRegister>(Endpoints.AuthRegister, registerRequest).ConfigureAwait(false);
return registerResponse;
}
public async Task<Responses.AuthLogin> Login(string email, string password)
{
var request = new Requests.AuthLogin { Email = email, Password = password };
var response = await _rest.Post<Responses.AuthLogin>(Endpoints.AuthLogin, request).ConfigureAwait(false);
return response;
}
public Task Logout()
=> _rest.Post(Endpoints.AuthLogout);

//Servers
public Task<Responses.CreateServer> CreateServer(string name, string region)
{
var request = new Requests.CreateServer { Name = name, Region = region };
return _rest.Post<Responses.CreateServer>(Endpoints.Servers, request);
}
public Task LeaveServer(string id)
=> _rest.Delete<Responses.DeleteServer>(Endpoints.Server(id));

//Channels
public Task<Responses.CreateChannel> CreateChannel(string serverId, string name, string channelType)
{
var request = new Requests.CreateChannel { Name = name, Type = channelType };
return _rest.Post<Responses.CreateChannel>(Endpoints.ServerChannels(serverId), request);
}
public Task<Responses.CreateChannel> CreatePMChannel(string myId, string recipientId)
{
var request = new Requests.CreatePMChannel { RecipientId = recipientId };
return _rest.Post<Responses.CreateChannel>(Endpoints.UserChannels(myId), request);
}
public Task<Responses.DestroyChannel> DestroyChannel(string channelId)
=> _rest.Delete<Responses.DestroyChannel>(Endpoints.Channel(channelId));
public Task<Responses.GetMessages[]> GetMessages(string channelId, int count)
=> _rest.Get<Responses.GetMessages[]>(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<Responses.CreateInvite> 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<Responses.CreateInvite>(Endpoints.ChannelInvites(channelId), request);
}
public Task<Responses.GetInvite> GetInvite(string id)
=> _rest.Get<Responses.GetInvite>(Endpoints.Invite(id));
public Task AcceptInvite(string id)
=> _rest.Post<Responses.AcceptInvite>(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<Responses.SendMessage> 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<Responses.SendMessage>(Endpoints.ChannelMessages(channelId), request);
}
public Task<Responses.EditMessage> EditMessage(string messageId, string channelId, string message, string[] mentions)
{
var request = new Requests.EditMessage { Content = message, Mentions = mentions };
return _rest.Patch<Responses.EditMessage>(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<Responses.SendMessage>(Endpoints.ChannelMessages(channelId), filePath);

//Voice
public Task<Responses.GetRegions[]> GetVoiceRegions()
=> _rest.Get<Responses.GetRegions[]>(Endpoints.VoiceRegions);
public Task<Responses.GetIce> GetVoiceIce()
=> _rest.Get<Responses.GetIce>(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<Responses.ChangeProfile> ChangeUsername(string newUsername, string currentEmail, string currentPassword)
{
var request = new Requests.ChangeUsername { Username = newUsername, CurrentEmail = currentEmail, CurrentPassword = currentPassword };
return _rest.Patch<Responses.ChangeProfile>(Endpoints.UserMe, request);
}
public Task<Responses.ChangeProfile> ChangeEmail(string newEmail, string currentPassword)
{
var request = new Requests.ChangeEmail { NewEmail = newEmail, CurrentPassword = currentPassword };
return _rest.Patch<Responses.ChangeProfile>(Endpoints.UserMe, request);
}
public Task<Responses.ChangeProfile> ChangePassword(string newPassword, string currentEmail, string currentPassword)
{
var request = new Requests.ChangePassword { NewPassword = newPassword, CurrentEmail = currentEmail, CurrentPassword = currentPassword };
return _rest.Patch<Responses.ChangeProfile>(Endpoints.UserMe, request);
}
public Task<Responses.ChangeProfile> 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<Responses.ChangeProfile>(Endpoints.UserMe, request);
}

//Other
/*public Task<Responses.Status> GetUnresolvedIncidents()
{
return _rest.Get<Responses.Status>(Endpoints.StatusUnresolvedMaintenance);
}
public Task<Responses.Status> GetActiveIncidents()
{
return _rest.Get<Responses.Status>(Endpoints.StatusActiveMaintenance);
}*/
}
}

+ 0
- 158
src/Discord.Net/Net/API/Requests.cs View File

@@ -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;
}
}
}

+ 0
- 85
src/Discord.Net/Net/API/Responses.cs View File

@@ -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;
}
}
}
}

+ 0
- 71
src/Discord.Net/Net/WebSockets/Commands.cs View File

@@ -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<ulong>
{
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<Login.Data>
{
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<string, string> Properties = new Dictionary<string, string>();
}
}
public sealed class UpdateStatus : WebSocketMessage<UpdateStatus.Data>
{
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<JoinVoice.Data>
{
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<Resume.Data>
{
public Resume() : base(6) { }
public class Data
{
[JsonProperty(PropertyName = "session_id")]
public string SessionId;
[JsonProperty(PropertyName = "seq")]
public int Sequence;
}
}
}
}

+ 0
- 118
src/Discord.Net/Net/WebSockets/Events.cs View File

@@ -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;
}
}
}

+ 0
- 62
src/Discord.Net/Net/WebSockets/VoiceCommands.cs View File

@@ -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<Login.Data>
{
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<Login2.Data>
{
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<object>
{
public KeepAlive() : base(3, null) { }
}
public sealed class IsTalking : WebSocketMessage<IsTalking.Data>
{
public IsTalking() : base(5) { }
public class Data
{
[JsonProperty(PropertyName = "delay")]
public int Delay;
[JsonProperty(PropertyName = "speaking")]
public bool IsSpeaking;
}
}
}
}

+ 0
- 41
src/Discord.Net/Net/WebSockets/VoiceEvents.cs View File

@@ -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;
}
}
}

+ 0
- 4
src/Discord.Net/TimeoutException.cs View File

@@ -1,8 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace Discord namespace Discord
{ {


+ 67
- 0
src/Discord.Net/WebSockets/Data/Commands.cs View File

@@ -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<ulong>
{
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<LoginCommand.Data>
{
public LoginCommand() : base(2) { }
public class Data
{
[JsonProperty("token")]
public string Token;
[JsonProperty("v")]
public int Version = 3;
[JsonProperty("properties")]
public Dictionary<string, string> Properties = new Dictionary<string, string>();
}
}
internal sealed class UpdateStatusCommand : WebSocketMessage<UpdateStatusCommand.Data>
{
public UpdateStatusCommand() : base(3) { }
public class Data
{
[JsonProperty("idle_since")]
public string IdleSince;
[JsonProperty("game_id")]
public string GameId;
}
}
internal sealed class JoinVoiceCommand : WebSocketMessage<JoinVoiceCommand.Data>
{
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<ResumeCommand.Data>
{
public ResumeCommand() : base(6) { }
public class Data
{
[JsonProperty("session_id")]
public string SessionId;
[JsonProperty("seq")]
public int Sequence;
}
}
}

src/Discord.Net/Net/WebSockets/DataWebSocket.cs → src/Discord.Net/WebSockets/Data/DataWebSocket.cs View File

@@ -1,10 +1,9 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord.Net.WebSockets
namespace Discord.WebSockets.Data
{ {
internal partial class DataWebSocket : WebSocket internal partial class DataWebSocket : WebSocket
{ {
@@ -22,7 +21,7 @@ namespace Discord.Net.WebSockets
{ {
await Connect().ConfigureAwait(false); await Connect().ConfigureAwait(false);
Commands.Login msg = new Commands.Login();
LoginCommand msg = new LoginCommand();
msg.Payload.Token = token; msg.Payload.Token = token;
msg.Payload.Properties["$device"] = "Discord.Net"; msg.Payload.Properties["$device"] = "Discord.Net";
QueueMessage(msg); QueueMessage(msg);
@@ -32,7 +31,7 @@ namespace Discord.Net.WebSockets
await DisconnectInternal(isUnexpected: false).ConfigureAwait(false); await DisconnectInternal(isUnexpected: false).ConfigureAwait(false);
await Connect().ConfigureAwait(false); await Connect().ConfigureAwait(false);


var resumeMsg = new Commands.Resume();
var resumeMsg = new ResumeCommand();
resumeMsg.Payload.SessionId = _sessionId; resumeMsg.Payload.SessionId = _sessionId;
resumeMsg.Payload.Sequence = _lastSeq; resumeMsg.Payload.Sequence = _lastSeq;
QueueMessage(resumeMsg); QueueMessage(resumeMsg);
@@ -75,16 +74,16 @@ namespace Discord.Net.WebSockets
JToken token = msg.Payload as JToken; JToken token = msg.Payload as JToken;
if (msg.Type == "READY") if (msg.Type == "READY")
{ {
var payload = token.ToObject<Events.Ready>();
var payload = token.ToObject<ReadyEvent>();
_sessionId = payload.SessionId; _sessionId = payload.SessionId;
_heartbeatInterval = payload.HeartbeatInterval; _heartbeatInterval = payload.HeartbeatInterval;
QueueMessage(new Commands.UpdateStatus());
QueueMessage(new UpdateStatusCommand());
} }
else if (msg.Type == "RESUMED") else if (msg.Type == "RESUMED")
{ {
var payload = token.ToObject<Events.Resumed>();
var payload = token.ToObject<ResumedEvent>();
_heartbeatInterval = payload.HeartbeatInterval; _heartbeatInterval = payload.HeartbeatInterval;
QueueMessage(new Commands.UpdateStatus());
QueueMessage(new UpdateStatusCommand());
} }
RaiseReceivedEvent(msg.Type, token); RaiseReceivedEvent(msg.Type, token);
if (msg.Type == "READY" || msg.Type == "RESUMED") if (msg.Type == "READY" || msg.Type == "RESUMED")
@@ -93,7 +92,7 @@ namespace Discord.Net.WebSockets
break; break;
case 7: //Redirect case 7: //Redirect
{ {
var payload = (msg.Payload as JToken).ToObject<Events.Redirect>();
var payload = (msg.Payload as JToken).ToObject<RedirectEvent>();
Host = payload.Url; Host = payload.Url;
if (_logLevel >= LogMessageSeverity.Info) if (_logLevel >= LogMessageSeverity.Info)
RaiseOnLog(LogMessageSeverity.Info, "Redirected to " + payload.Url); RaiseOnLog(LogMessageSeverity.Info, "Redirected to " + payload.Url);
@@ -109,19 +108,19 @@ namespace Discord.Net.WebSockets


protected override object GetKeepAlive() protected override object GetKeepAlive()
{ {
return new Commands.KeepAlive();
return new KeepAliveCommand();
} }


public void SendJoinVoice(string serverId, string channelId) public void SendJoinVoice(string serverId, string channelId)
{ {
var joinVoice = new Commands.JoinVoice();
var joinVoice = new JoinVoiceCommand();
joinVoice.Payload.ServerId = serverId; joinVoice.Payload.ServerId = serverId;
joinVoice.Payload.ChannelId = channelId; joinVoice.Payload.ChannelId = channelId;
QueueMessage(joinVoice); QueueMessage(joinVoice);
} }
public void SendLeaveVoice(string serverId) public void SendLeaveVoice(string serverId)
{ {
var leaveVoice = new Commands.JoinVoice();
var leaveVoice = new JoinVoiceCommand();
leaveVoice.Payload.ServerId = serverId; leaveVoice.Payload.ServerId = serverId;
QueueMessage(leaveVoice); QueueMessage(leaveVoice);
} }

src/Discord.Net/Net/WebSockets/DataWebSockets.Events.cs → src/Discord.Net/WebSockets/Data/DataWebSockets.Events.cs View File

@@ -1,9 +1,9 @@
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; 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 string Type;
public readonly JToken Payload; public readonly JToken Payload;

+ 115
- 0
src/Discord.Net/WebSockets/Data/Events.cs View File

@@ -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;
}
}

+ 59
- 0
src/Discord.Net/WebSockets/Voice/Commands.cs View File

@@ -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<LoginCommand.Data>
{
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<Login2Command.Data>
{
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<object>
{
public KeepAliveCommand() : base(3, null) { }
}
internal sealed class IsTalkingCommand : WebSocketMessage<IsTalkingCommand.Data>
{
public IsTalkingCommand() : base(5) { }
public class Data
{
[JsonProperty("delay")]
public int Delay;
[JsonProperty("speaking")]
public bool IsSpeaking;
}
}
}

+ 38
- 0
src/Discord.Net/WebSockets/Voice/Events.cs View File

@@ -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;
}
}

src/Discord.Net/Net/WebSockets/VoiceWebSocket.Events.cs → src/Discord.Net/WebSockets/Voice/VoiceWebSocket.Events.cs View File

@@ -1,6 +1,6 @@
using System; using System;


namespace Discord.Net.WebSockets
namespace Discord.WebSockets.Voice
{ {
public sealed class IsTalkingEventArgs : EventArgs public sealed class IsTalkingEventArgs : EventArgs
{ {

src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs → src/Discord.Net/WebSockets/Voice/VoiceWebSocket.cs View File

@@ -12,7 +12,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord.Net.WebSockets
namespace Discord.WebSockets.Voice
{ {
internal partial class VoiceWebSocket : WebSocket internal partial class VoiceWebSocket : WebSocket
{ {
@@ -106,7 +106,7 @@ namespace Discord.Net.WebSockets
_udp.AllowNatTraversal(true); _udp.AllowNatTraversal(true);
#endif #endif
VoiceCommands.Login msg = new VoiceCommands.Login();
LoginCommand msg = new LoginCommand();
msg.Payload.ServerId = _serverId; msg.Payload.ServerId = _serverId;
msg.Payload.SessionId = _sessionId; msg.Payload.SessionId = _sessionId;
msg.Payload.Token = _token; msg.Payload.Token = _token;
@@ -294,7 +294,7 @@ namespace Discord.Net.WebSockets
{ {
if (_state != (int)WebSocketState.Connected) if (_state != (int)WebSocketState.Connected)
{ {
var payload = (msg.Payload as JToken).ToObject<VoiceEvents.Ready>();
var payload = (msg.Payload as JToken).ToObject<ReadyEvent>();
_heartbeatInterval = payload.HeartbeatInterval; _heartbeatInterval = payload.HeartbeatInterval;
_ssrc = payload.SSRC; _ssrc = payload.SSRC;
_endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(), payload.Port); _endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(), payload.Port);
@@ -319,7 +319,7 @@ namespace Discord.Net.WebSockets
break; break;
case 4: //SESSION_DESCRIPTION case 4: //SESSION_DESCRIPTION
{ {
var payload = (msg.Payload as JToken).ToObject<VoiceEvents.JoinServer>();
var payload = (msg.Payload as JToken).ToObject<JoinServerEvent>();
_secretKey = payload.SecretKey; _secretKey = payload.SecretKey;
SendIsTalking(true); SendIsTalking(true);
_connectWaitOnLogin.Set(); _connectWaitOnLogin.Set();
@@ -327,7 +327,7 @@ namespace Discord.Net.WebSockets
break; break;
case 5: case 5:
{ {
var payload = (msg.Payload as JToken).ToObject<VoiceEvents.IsTalking>();
var payload = (msg.Payload as JToken).ToObject<IsTalkingEvent>();
RaiseIsSpeaking(payload.UserId, payload.IsSpeaking); RaiseIsSpeaking(payload.UserId, payload.IsSpeaking);
} }
break; break;
@@ -358,7 +358,7 @@ namespace Discord.Net.WebSockets


CompleteConnect(); CompleteConnect();


var login2 = new VoiceCommands.Login2();
var login2 = new Login2Command();
login2.Payload.Protocol = "udp"; login2.Payload.Protocol = "udp";
login2.Payload.SocketData.Address = ip; login2.Payload.SocketData.Address = ip;
login2.Payload.SocketData.Mode = _isEncrypted ? EncryptedMode : UnencryptedMode; login2.Payload.SocketData.Mode = _isEncrypted ? EncryptedMode : UnencryptedMode;
@@ -503,7 +503,7 @@ namespace Discord.Net.WebSockets


private void SendIsTalking(bool value) private void SendIsTalking(bool value)
{ {
var isTalking = new VoiceCommands.IsTalking();
var isTalking = new IsTalkingCommand();
isTalking.Payload.IsSpeaking = value; isTalking.Payload.IsSpeaking = value;
isTalking.Payload.Delay = 0; isTalking.Payload.Delay = 0;
QueueMessage(isTalking); QueueMessage(isTalking);
@@ -511,7 +511,7 @@ namespace Discord.Net.WebSockets


protected override object GetKeepAlive() protected override object GetKeepAlive()
{ {
return new VoiceCommands.KeepAlive();
return new KeepAliveCommand();
} }


public void WaitForQueue() public void WaitForQueue()

src/Discord.Net/Net/WebSockets/WebSocket.BuiltIn.cs → src/Discord.Net/WebSockets/WebSocket.BuiltIn.cs View File

@@ -8,7 +8,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using State = System.Net.WebSockets.WebSocketState; using State = System.Net.WebSockets.WebSocketState;


namespace Discord.Net.WebSockets
namespace Discord.WebSockets
{ {
internal class BuiltInWebSocketEngine : IWebSocketEngine internal class BuiltInWebSocketEngine : IWebSocketEngine
{ {

src/Discord.Net/Net/WebSockets/WebSocket.Events.cs → src/Discord.Net/WebSockets/WebSocket.Events.cs View File

@@ -1,6 +1,6 @@
using System; using System;


namespace Discord.Net.WebSockets
namespace Discord.WebSockets
{ {
internal partial class WebSocket internal partial class WebSocket
{ {

src/Discord.Net/Net/WebSockets/WebSocket.cs → src/Discord.Net/WebSockets/WebSocket.cs View File

@@ -7,7 +7,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord.Net.WebSockets
namespace Discord.WebSockets
{ {
public enum WebSocketState : byte public enum WebSocketState : byte
{ {
@@ -88,7 +88,7 @@ namespace Discord.Net.WebSockets
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelTokenSource.Token, ParentCancelToken).Token; _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelTokenSource.Token, ParentCancelToken).Token;
else else
_cancelToken = _cancelTokenSource.Token; _cancelToken = _cancelTokenSource.Token;
await _engine.Connect(Host, _cancelToken).ConfigureAwait(false); await _engine.Connect(Host, _cancelToken).ConfigureAwait(false);
_lastHeartbeat = DateTime.UtcNow; _lastHeartbeat = DateTime.UtcNow;


@@ -107,8 +107,6 @@ namespace Discord.Net.WebSockets
_connectedEvent.Set(); _connectedEvent.Set();
RaiseConnected(); RaiseConnected();
} }
/*public Task Reconnect(CancellationToken cancelToken)
=> Connect(_host, _cancelToken);*/


public Task Disconnect() => DisconnectInternal(new Exception("Disconnect was requested by user."), isUnexpected: false); 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) protected async Task DisconnectInternal(Exception ex = null, bool isUnexpected = true, bool skipAwait = false)

src/Discord.Net/Net/WebSockets/WebSocketMessage.cs → src/Discord.Net/WebSockets/WebSocketMessage.cs View File

@@ -1,17 +1,17 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;


namespace Discord.Net.WebSockets
namespace Discord.WebSockets
{ {
public class WebSocketMessage public class WebSocketMessage
{ {
[JsonProperty(PropertyName = "op")]
[JsonProperty("op")]
public int Operation; public int Operation;
[JsonProperty(PropertyName = "d")]
[JsonProperty("d")]
public object Payload; public object Payload;
[JsonProperty(PropertyName = "t", NullValueHandling = NullValueHandling.Ignore)]
[JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)]
public string Type; public string Type;
[JsonProperty(PropertyName = "s", NullValueHandling = NullValueHandling.Ignore)]
[JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)]
public int? Sequence; public int? Sequence;
} }
internal abstract class WebSocketMessage<T> : WebSocketMessage internal abstract class WebSocketMessage<T> : WebSocketMessage

Loading…
Cancel
Save