Browse Source

Started adding support for voice

tags/docs-0.9
Brandon Smith 10 years ago
parent
commit
a7c4bfa595
14 changed files with 438 additions and 155 deletions
  1. +1
    -1
      global.json
  2. +22
    -5
      src/Discord.Net.Net45/Discord.Net.csproj
  3. +0
    -16
      src/Discord.Net.Net45/HttpException.cs
  4. +1
    -1
      src/Discord.Net/API/Models/TextWebSocketCommands.cs
  5. +1
    -2
      src/Discord.Net/API/Models/TextWebSocketEvents.cs
  6. +53
    -0
      src/Discord.Net/API/Models/VoiceWebSocketCommands.cs
  7. +12
    -0
      src/Discord.Net/API/Models/VoiceWebSocketEvents.cs
  8. +6
    -0
      src/Discord.Net/DiscordClient.Events.cs
  9. +76
    -37
      src/Discord.Net/DiscordClient.cs
  10. +25
    -0
      src/Discord.Net/DiscordTextWebSocket.Events.cs
  11. +82
    -0
      src/Discord.Net/DiscordTextWebSocket.cs
  12. +125
    -0
      src/Discord.Net/DiscordVoiceWebSocket.cs
  13. +3
    -21
      src/Discord.Net/DiscordWebSocket.Events.cs
  14. +31
    -72
      src/Discord.Net/DiscordWebSocket.cs

+ 1
- 1
global.json View File

@@ -3,6 +3,6 @@
"sdk": { "sdk": {
"version": "1.0.0-beta6", "version": "1.0.0-beta6",
"architecture": "x64", "architecture": "x64",
"runtime": "coreclr"
"runtime": "clr"
} }
} }

+ 22
- 5
src/Discord.Net.Net45/Discord.Net.csproj View File

@@ -58,11 +58,17 @@
<Compile Include="..\Discord.Net\API\Models\Common.cs"> <Compile Include="..\Discord.Net\API\Models\Common.cs">
<Link>API\Models\Common.cs</Link> <Link>API\Models\Common.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\API\Models\WebSocketCommands.cs">
<Link>API\Models\WebSocketCommands.cs</Link>
<Compile Include="..\Discord.Net\API\Models\TextWebSocketCommands.cs">
<Link>API\Models\TextWebSocketCommands.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\API\Models\WebSocketEvents.cs">
<Link>API\Models\WebSocketEvents.cs</Link>
<Compile Include="..\Discord.Net\API\Models\TextWebSocketEvents.cs">
<Link>API\Models\TextWebSocketEvents.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\API\Models\VoiceWebSocketCommands.cs">
<Link>API\Models\VoiceWebSocketCommands.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\API\Models\VoiceWebSocketEvents.cs">
<Link>API\Models\VoiceWebSocketEvents.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Channel.cs"> <Compile Include="..\Discord.Net\Channel.cs">
<Link>Channel.cs</Link> <Link>Channel.cs</Link>
@@ -79,6 +85,15 @@
<Compile Include="..\Discord.Net\DiscordClientConfig.cs"> <Compile Include="..\Discord.Net\DiscordClientConfig.cs">
<Link>DiscordClientConfig.cs</Link> <Link>DiscordClientConfig.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\DiscordTextWebSocket.cs">
<Link>DiscordTextWebSocket.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\DiscordTextWebSocket.Events.cs">
<Link>DiscordTextWebSocket.Events.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\DiscordVoiceWebSocket.cs">
<Link>DiscordVoiceWebSocket.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\DiscordWebSocket.cs"> <Compile Include="..\Discord.Net\DiscordWebSocket.cs">
<Link>DiscordWebSocket.cs</Link> <Link>DiscordWebSocket.cs</Link>
</Compile> </Compile>
@@ -91,6 +106,9 @@
<Compile Include="..\Discord.Net\Helpers\Http.cs"> <Compile Include="..\Discord.Net\Helpers\Http.cs">
<Link>Helpers\Http.cs</Link> <Link>Helpers\Http.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\HttpException.cs">
<Link>HttpException.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Invite.cs"> <Compile Include="..\Discord.Net\Invite.cs">
<Link>Invite.cs</Link> <Link>Invite.cs</Link>
</Compile> </Compile>
@@ -115,7 +133,6 @@
<Compile Include="..\Discord.Net\User.cs"> <Compile Include="..\Discord.Net\User.cs">
<Link>User.cs</Link> <Link>User.cs</Link>
</Compile> </Compile>
<Compile Include="HttpException.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />


+ 0
- 16
src/Discord.Net.Net45/HttpException.cs View File

@@ -1,16 +0,0 @@
using System;
using System.Net;

namespace Discord
{
public class HttpException : Exception
{
public HttpStatusCode StatusCode { get; }

public HttpException(HttpStatusCode statusCode)
: base($"The server responded with error {statusCode}")
{
StatusCode = statusCode;
}
}
}

src/Discord.Net/API/Models/WebSocketCommands.cs → src/Discord.Net/API/Models/TextWebSocketCommands.cs View File

@@ -8,7 +8,7 @@ using System.Collections.Generic;


namespace Discord.API.Models namespace Discord.API.Models
{ {
internal static class WebSocketCommands
internal static class TextWebSocketCommands
{ {
public sealed class KeepAlive : WebSocketMessage<int> public sealed class KeepAlive : WebSocketMessage<int>
{ {

src/Discord.Net/API/Models/WebSocketEvents.cs → src/Discord.Net/API/Models/TextWebSocketEvents.cs View File

@@ -3,11 +3,10 @@
#pragma warning disable CS0169 #pragma warning disable CS0169


using Newtonsoft.Json; using Newtonsoft.Json;
using System;


namespace Discord.API.Models namespace Discord.API.Models
{ {
internal static class WebSocketEvents
internal static class TextWebSocketEvents
{ {
public sealed class Ready public sealed class Ready
{ {

+ 53
- 0
src/Discord.Net/API/Models/VoiceWebSocketCommands.cs View File

@@ -0,0 +1,53 @@
//Ignore unused/unassigned variable warnings
#pragma warning disable CS0649
#pragma warning disable CS0169

using Newtonsoft.Json;
using System;
using System.Collections.Generic;

namespace Discord.API.Models
{
internal static class VoiceWebSocketCommands
{
public sealed class KeepAlive : WebSocketMessage<object>
{
public KeepAlive() : base(3, null) { }
}
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<Login.Data>
{
public Login2() : base(1) { }
public class Data
{
public class PCData
{
[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 = "token")]
public string Token;
}
}
}
}

+ 12
- 0
src/Discord.Net/API/Models/VoiceWebSocketEvents.cs View File

@@ -0,0 +1,12 @@
//Ignore unused/unassigned variable warnings
#pragma warning disable CS0649
#pragma warning disable CS0169

using Newtonsoft.Json;

namespace Discord.API.Models
{
internal static class VoiceWebSocketEvents
{
}
}

+ 6
- 0
src/Discord.Net/DiscordClient.Events.cs View File

@@ -16,6 +16,12 @@ namespace Discord
if (DebugMessage != null) if (DebugMessage != null)
DebugMessage(this, new LogMessageEventArgs(message)); DebugMessage(this, new LogMessageEventArgs(message));
} }
public event EventHandler<LogMessageEventArgs> VoiceDebugMessage;
private void RaiseOnVoiceDebugMessage(string message)
{
if (VoiceDebugMessage != null)
VoiceDebugMessage(this, new LogMessageEventArgs(message));
}


//General //General
public event EventHandler Connected; public event EventHandler Connected;


+ 76
- 37
src/Discord.Net/DiscordClient.cs View File

@@ -17,16 +17,22 @@ namespace Discord
/// <summary> Provides a connection to the DiscordApp service. </summary> /// <summary> Provides a connection to the DiscordApp service. </summary>
public partial class DiscordClient public partial class DiscordClient
{ {
private DiscordClientConfig _config;
private DiscordWebSocket _webSocket;
private ManualResetEventSlim _blockEvent;
private volatile CancellationTokenSource _disconnectToken;
private volatile Task _tasks;
private readonly DiscordClientConfig _config;
private readonly DiscordTextWebSocket _webSocket;
private readonly ManualResetEventSlim _blockEvent;
private readonly Regex _userRegex, _channelRegex; private readonly Regex _userRegex, _channelRegex;
private readonly MatchEvaluator _userRegexEvaluator, _channelRegexEvaluator; private readonly MatchEvaluator _userRegexEvaluator, _channelRegexEvaluator;
private readonly JsonSerializer _serializer; private readonly JsonSerializer _serializer;
private readonly Random _rand; private readonly Random _rand;


private volatile CancellationTokenSource _disconnectToken;
private volatile Task _tasks;

#if !DNXCORE50
private readonly DiscordVoiceWebSocket _voiceWebSocket;
private string _currentVoiceServer;
#endif

/// <summary> Returns the User object for the current logged in user. </summary> /// <summary> Returns the User object for the current logged in user. </summary>
public User User { get; private set; } public User User { get; private set; }
/// <summary> Returns the id of the current logged in user. </summary> /// <summary> Returns the id of the current logged in user. </summary>
@@ -276,7 +282,7 @@ namespace Discord
user => { } user => { }
); );


_webSocket = new DiscordWebSocket(_config.WebSocketInterval);
_webSocket = new DiscordTextWebSocket(_config.WebSocketInterval);
_webSocket.Connected += (s, e) => RaiseConnected(); _webSocket.Connected += (s, e) => RaiseConnected();
_webSocket.Disconnected += async (s, e) => _webSocket.Disconnected += async (s, e) =>
{ {
@@ -287,7 +293,8 @@ namespace Discord
try try
{ {
await Task.Delay(_config.ReconnectDelay); await Task.Delay(_config.ReconnectDelay);
await _webSocket.ConnectAsync(Endpoints.WebSocket_Hub, true);
await _webSocket.ConnectAsync(Endpoints.WebSocket_Hub);
await _webSocket.Login();
break; break;
} }
catch (Exception ex) catch (Exception ex)
@@ -298,6 +305,35 @@ namespace Discord
} }
} }
}; };
_webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Message);

#if !DNXCORE50
_voiceWebSocket = new DiscordVoiceWebSocket(_config.WebSocketInterval);
_voiceWebSocket.Connected += (s, e) => RaiseConnected();
_voiceWebSocket.Disconnected += async (s, e) =>
{
//Reconnect if we didn't cause the disconnect
RaiseDisconnected();
while (!_disconnectToken.IsCancellationRequested)
{
try
{
await Task.Delay(_config.ReconnectDelay);
await _voiceWebSocket.ConnectAsync(Endpoints.WebSocket_Hub);
await _voiceWebSocket.Login(_currentVoiceServer, UserId, SessionId);
break;
}
catch (Exception ex)
{
RaiseOnDebugMessage($"Reconnect Failed: {ex.Message}");
//Net is down? We can keep trying to reconnect until the user runs Disconnect()
await Task.Delay(_config.FailedReconnectDelay);
}
}
};
_voiceWebSocket.OnDebugMessage += (s, e) => RaiseOnVoiceDebugMessage(e.Message);
#endif

_webSocket.GotEvent += (s, e) => _webSocket.GotEvent += (s, e) =>
{ {
switch (e.Type) switch (e.Type)
@@ -305,7 +341,7 @@ namespace Discord
//Global //Global
case "READY": //Resync case "READY": //Resync
{ {
var data = e.Event.ToObject<WebSocketEvents.Ready>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.Ready>(_serializer);


_servers.Clear(); _servers.Clear();
_channels.Clear(); _channels.Clear();
@@ -324,21 +360,21 @@ namespace Discord
//Servers //Servers
case "GUILD_CREATE": case "GUILD_CREATE":
{ {
var data = e.Event.ToObject<WebSocketEvents.GuildCreate>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.GuildCreate>(_serializer);
var server = _servers.Update(data.Id, data); var server = _servers.Update(data.Id, data);
try { RaiseServerCreated(server); } catch { } try { RaiseServerCreated(server); } catch { }
} }
break; break;
case "GUILD_UPDATE": case "GUILD_UPDATE":
{ {
var data = e.Event.ToObject<WebSocketEvents.GuildUpdate>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.GuildUpdate>(_serializer);
var server = _servers.Update(data.Id, data); var server = _servers.Update(data.Id, data);
try { RaiseServerUpdated(server); } catch { } try { RaiseServerUpdated(server); } catch { }
} }
break; break;
case "GUILD_DELETE": case "GUILD_DELETE":
{ {
var data = e.Event.ToObject<WebSocketEvents.GuildDelete>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.GuildDelete>(_serializer);
var server = _servers.Remove(data.Id); var server = _servers.Remove(data.Id);
if (server != null) if (server != null)
try { RaiseServerDestroyed(server); } catch { } try { RaiseServerDestroyed(server); } catch { }
@@ -348,21 +384,21 @@ namespace Discord
//Channels //Channels
case "CHANNEL_CREATE": case "CHANNEL_CREATE":
{ {
var data = e.Event.ToObject<WebSocketEvents.ChannelCreate>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.ChannelCreate>(_serializer);
var channel = _channels.Update(data.Id, data.GuildId, data); var channel = _channels.Update(data.Id, data.GuildId, data);
try { RaiseChannelCreated(channel); } catch { } try { RaiseChannelCreated(channel); } catch { }
} }
break; break;
case "CHANNEL_UPDATE": case "CHANNEL_UPDATE":
{ {
var data = e.Event.ToObject<WebSocketEvents.ChannelUpdate>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.ChannelUpdate>(_serializer);
var channel = _channels.Update(data.Id, data.GuildId, data); var channel = _channels.Update(data.Id, data.GuildId, data);
try { RaiseChannelUpdated(channel); } catch { } try { RaiseChannelUpdated(channel); } catch { }
} }
break; break;
case "CHANNEL_DELETE": case "CHANNEL_DELETE":
{ {
var data = e.Event.ToObject<WebSocketEvents.ChannelDelete>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.ChannelDelete>(_serializer);
var channel = _channels.Remove(data.Id); var channel = _channels.Remove(data.Id);
if (channel != null) if (channel != null)
try { RaiseChannelDestroyed(channel); } catch { } try { RaiseChannelDestroyed(channel); } catch { }
@@ -372,7 +408,7 @@ namespace Discord
//Members //Members
case "GUILD_MEMBER_ADD": case "GUILD_MEMBER_ADD":
{ {
var data = e.Event.ToObject<WebSocketEvents.GuildMemberAdd>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.GuildMemberAdd>(_serializer);
var user = _users.Update(data.User.Id, data.User); var user = _users.Update(data.User.Id, data.User);
var server = _servers[data.ServerId]; var server = _servers[data.ServerId];
if (server != null) if (server != null)
@@ -384,7 +420,7 @@ namespace Discord
break; break;
case "GUILD_MEMBER_UPDATE": case "GUILD_MEMBER_UPDATE":
{ {
var data = e.Event.ToObject<WebSocketEvents.GuildMemberUpdate>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.GuildMemberUpdate>(_serializer);
var user = _users.Update(data.User.Id, data.User); var user = _users.Update(data.User.Id, data.User);
var server = _servers[data.ServerId]; var server = _servers[data.ServerId];
if (server != null) if (server != null)
@@ -396,7 +432,7 @@ namespace Discord
break; break;
case "GUILD_MEMBER_REMOVE": case "GUILD_MEMBER_REMOVE":
{ {
var data = e.Event.ToObject<WebSocketEvents.GuildMemberRemove>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.GuildMemberRemove>(_serializer);
var server = _servers[data.ServerId]; var server = _servers[data.ServerId];
if (server != null) if (server != null)
{ {
@@ -410,21 +446,21 @@ namespace Discord
//Roles //Roles
case "GUILD_ROLE_CREATE": case "GUILD_ROLE_CREATE":
{ {
var data = e.Event.ToObject<WebSocketEvents.GuildRoleCreateUpdate>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.GuildRoleCreateUpdate>(_serializer);
var role = _roles.Update(data.Role.Id, data.ServerId, data.Role); var role = _roles.Update(data.Role.Id, data.ServerId, data.Role);
try { RaiseRoleCreated(role); } catch { } try { RaiseRoleCreated(role); } catch { }
} }
break; break;
case "GUILD_ROLE_UPDATE": case "GUILD_ROLE_UPDATE":
{ {
var data = e.Event.ToObject<WebSocketEvents.GuildRoleCreateUpdate>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.GuildRoleCreateUpdate>(_serializer);
var role = _roles.Update(data.Role.Id, data.ServerId, data.Role); var role = _roles.Update(data.Role.Id, data.ServerId, data.Role);
try { RaiseRoleUpdated(role); } catch { } try { RaiseRoleUpdated(role); } catch { }
} }
break; break;
case "GUILD_ROLE_DELETE": case "GUILD_ROLE_DELETE":
{ {
var data = e.Event.ToObject<WebSocketEvents.GuildRoleDelete>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.GuildRoleDelete>(_serializer);
var role = _roles.Remove(data.RoleId); var role = _roles.Remove(data.RoleId);
if (role != null) if (role != null)
try { RaiseRoleDeleted(role); } catch { } try { RaiseRoleDeleted(role); } catch { }
@@ -434,7 +470,7 @@ namespace Discord
//Bans //Bans
case "GUILD_BAN_ADD": case "GUILD_BAN_ADD":
{ {
var data = e.Event.ToObject<WebSocketEvents.GuildBanAddRemove>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.GuildBanAddRemove>(_serializer);
var user = _users.Update(data.User.Id, data.User); var user = _users.Update(data.User.Id, data.User);
var server = _servers[data.ServerId]; var server = _servers[data.ServerId];
try { RaiseBanAdded(user, server); } catch { } try { RaiseBanAdded(user, server); } catch { }
@@ -442,7 +478,7 @@ namespace Discord
break; break;
case "GUILD_BAN_REMOVE": case "GUILD_BAN_REMOVE":
{ {
var data = e.Event.ToObject<WebSocketEvents.GuildBanAddRemove>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.GuildBanAddRemove>(_serializer);
var user = _users.Update(data.User.Id, data.User); var user = _users.Update(data.User.Id, data.User);
var server = _servers[data.ServerId]; var server = _servers[data.ServerId];
if (server != null && server.RemoveBan(user.Id)) if (server != null && server.RemoveBan(user.Id))
@@ -455,7 +491,7 @@ namespace Discord
//Messages //Messages
case "MESSAGE_CREATE": case "MESSAGE_CREATE":
{ {
var data = e.Event.ToObject<WebSocketEvents.MessageCreate>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.MessageCreate>(_serializer);
Message msg = null; Message msg = null;
bool wasLocal = _config.UseMessageQueue && data.Author.Id == UserId && data.Nonce != null; bool wasLocal = _config.UseMessageQueue && data.Author.Id == UserId && data.Nonce != null;
if (wasLocal) if (wasLocal)
@@ -478,14 +514,14 @@ namespace Discord
break; break;
case "MESSAGE_UPDATE": case "MESSAGE_UPDATE":
{ {
var data = e.Event.ToObject<WebSocketEvents.MessageUpdate>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.MessageUpdate>(_serializer);
var msg = _messages.Update(data.Id, data.ChannelId, data); var msg = _messages.Update(data.Id, data.ChannelId, data);
try { RaiseMessageUpdated(msg); } catch { } try { RaiseMessageUpdated(msg); } catch { }
} }
break; break;
case "MESSAGE_DELETE": case "MESSAGE_DELETE":
{ {
var data = e.Event.ToObject<WebSocketEvents.MessageDelete>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.MessageDelete>(_serializer);
var msg = GetMessage(data.MessageId); var msg = GetMessage(data.MessageId);
if (msg != null) if (msg != null)
{ {
@@ -496,7 +532,7 @@ namespace Discord
break; break;
case "MESSAGE_ACK": case "MESSAGE_ACK":
{ {
var data = e.Event.ToObject<WebSocketEvents.MessageAck>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.MessageAck>(_serializer);
var msg = GetMessage(data.MessageId); var msg = GetMessage(data.MessageId);
if (msg != null) if (msg != null)
try { RaiseMessageRead(msg); } catch { } try { RaiseMessageRead(msg); } catch { }
@@ -506,7 +542,7 @@ namespace Discord
//Statuses //Statuses
case "PRESENCE_UPDATE": case "PRESENCE_UPDATE":
{ {
var data = e.Event.ToObject<WebSocketEvents.PresenceUpdate>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.PresenceUpdate>(_serializer);
var user = _users.Update(data.User.Id, data.User); var user = _users.Update(data.User.Id, data.User);
var server = _servers[data.ServerId]; var server = _servers[data.ServerId];
if (server != null) if (server != null)
@@ -518,7 +554,7 @@ namespace Discord
break; break;
case "VOICE_STATE_UPDATE": case "VOICE_STATE_UPDATE":
{ {
var data = e.Event.ToObject<WebSocketEvents.VoiceStateUpdate>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.VoiceStateUpdate>(_serializer);
var server = _servers[data.ServerId]; var server = _servers[data.ServerId];
if (server != null) if (server != null)
{ {
@@ -530,7 +566,7 @@ namespace Discord
break; break;
case "TYPING_START": case "TYPING_START":
{ {
var data = e.Event.ToObject<WebSocketEvents.TypingStart>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.TypingStart>(_serializer);
var channel = _channels[data.ChannelId]; var channel = _channels[data.ChannelId];
var user = _users[data.UserId]; var user = _users[data.UserId];
if (user != null) if (user != null)
@@ -545,7 +581,7 @@ namespace Discord
//Voice //Voice
case "VOICE_SERVER_UPDATE": case "VOICE_SERVER_UPDATE":
{ {
var data = e.Event.ToObject<WebSocketEvents.VoiceServerUpdate>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.VoiceServerUpdate>(_serializer);
var server = _servers[data.ServerId]; var server = _servers[data.ServerId];
try { RaiseVoiceServerUpdated(server, data.Endpoint); } catch { } try { RaiseVoiceServerUpdated(server, data.Endpoint); } catch { }
} }
@@ -554,7 +590,7 @@ namespace Discord
//Settings //Settings
case "USER_UPDATE": case "USER_UPDATE":
{ {
var data = e.Event.ToObject<WebSocketEvents.UserUpdate>(_serializer);
var data = e.Event.ToObject<TextWebSocketEvents.UserUpdate>(_serializer);
var user = _users.Update(data.Id, data); var user = _users.Update(data.Id, data);
try { RaiseUserUpdated(user); } catch { } try { RaiseUserUpdated(user); } catch { }
} }
@@ -571,7 +607,6 @@ namespace Discord
break; break;
} }
}; };
_webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Message);
} }
private async Task SendAsync() private async Task SendAsync()
@@ -822,8 +857,9 @@ namespace Discord
{ {
try try
{ {
await _webSocket.ConnectAsync(Endpoints.WebSocket_Hub);
Http.Token = token; Http.Token = token;
await _webSocket.ConnectAsync(Endpoints.WebSocket_Hub, true);
await _webSocket.Login();
success = true; success = true;
} }
catch (InvalidOperationException) //Bad Token catch (InvalidOperationException) //Bad Token
@@ -835,27 +871,27 @@ namespace Discord
if (!success && password != null) //Email/Password login if (!success && password != null) //Email/Password login
{ {
//Open websocket while we wait for login response //Open websocket while we wait for login response
Task socketTask = _webSocket.ConnectAsync(Endpoints.WebSocket_Hub, false);
Task socketTask = _webSocket.ConnectAsync(Endpoints.WebSocket_Hub);
var response = await DiscordAPI.Login(emailOrUsername, password); var response = await DiscordAPI.Login(emailOrUsername, password);
await socketTask; await socketTask;


//Wait for websocket to finish connecting, then send token //Wait for websocket to finish connecting, then send token
token = response.Token; token = response.Token;
Http.Token = token; Http.Token = token;
_webSocket.Login();
await _webSocket.Login();
success = true; success = true;
} }
if (!success && password == null) //Anonymous login if (!success && password == null) //Anonymous login
{ {
//Open websocket while we wait for login response //Open websocket while we wait for login response
Task socketTask = _webSocket.ConnectAsync(Endpoints.WebSocket_Hub, false);
Task socketTask = _webSocket.ConnectAsync(Endpoints.WebSocket_Hub);
var response = await DiscordAPI.LoginAnonymous(emailOrUsername); var response = await DiscordAPI.LoginAnonymous(emailOrUsername);
await socketTask; await socketTask;


//Wait for websocket to finish connecting, then send token //Wait for websocket to finish connecting, then send token
token = response.Token; token = response.Token;
Http.Token = token; Http.Token = token;
_webSocket.Login();
await _webSocket.Login();
success = true; success = true;
} }
if (success) if (success)
@@ -868,6 +904,9 @@ namespace Discord
_tasks = _tasks.ContinueWith(async x => _tasks = _tasks.ContinueWith(async x =>
{ {
await _webSocket.DisconnectAsync(); await _webSocket.DisconnectAsync();
#if !DNXCORE50
await _voiceWebSocket.DisconnectAsync();
#endif


//Clear send queue //Clear send queue
Message ignored; Message ignored;


+ 25
- 0
src/Discord.Net/DiscordTextWebSocket.Events.cs View File

@@ -0,0 +1,25 @@
using Newtonsoft.Json.Linq;
using System;

namespace Discord
{
internal partial class DiscordTextWebSocket
{
public event EventHandler<MessageEventArgs> GotEvent;
public sealed class MessageEventArgs : EventArgs
{
public readonly string Type;
public readonly JToken Event;
internal MessageEventArgs(string type, JToken data)
{
Type = type;
Event = data;
}
}
private void RaiseGotEvent(string type, JToken payload)
{
if (GotEvent != null)
GotEvent(this, new MessageEventArgs(type, payload));
}
}
}

+ 82
- 0
src/Discord.Net/DiscordTextWebSocket.cs View File

@@ -0,0 +1,82 @@
using Discord.API.Models;
using Discord.Helpers;
using Newtonsoft.Json.Linq;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Discord
{
internal sealed partial class DiscordTextWebSocket : DiscordWebSocket
{
private const int ReadyTimeout = 2500; //Max time in milliseconds between connecting to Discord and receiving a READY event

private ManualResetEventSlim _connectWaitOnLogin, _connectWaitOnLogin2;

public DiscordTextWebSocket(int interval)
: base(interval)
{
_connectWaitOnLogin = new ManualResetEventSlim(false);
_connectWaitOnLogin2 = new ManualResetEventSlim(false);
}

public async Task Login()
{
var cancelToken = _disconnectToken.Token;

_connectWaitOnLogin.Reset();
_connectWaitOnLogin2.Reset();

TextWebSocketCommands.Login msg = new TextWebSocketCommands.Login();
msg.Payload.Token = Http.Token;
msg.Payload.Properties["$os"] = "";
msg.Payload.Properties["$browser"] = "";
msg.Payload.Properties["$device"] = "Discord.Net";
msg.Payload.Properties["$referrer"] = "";
msg.Payload.Properties["$referring_domain"] = "";
await SendMessage(msg, cancelToken);

try
{
if (!_connectWaitOnLogin.Wait(ReadyTimeout, cancelToken)) //Waiting on READY message
throw new Exception("No reply from Discord server");
}
catch (OperationCanceledException)
{
throw new InvalidOperationException("Bad Token");
}
try { _connectWaitOnLogin2.Wait(cancelToken); } //Waiting on READY handler
catch (OperationCanceledException) { return; }

SetConnected();
}

protected override void ProcessMessage(WebSocketMessage msg)
{
switch (msg.Operation)
{
case 0:
if (msg.Type == "READY")
{
var payload = (msg.Payload as JToken).ToObject<TextWebSocketEvents.Ready>();
_heartbeatInterval = payload.HeartbeatInterval;
QueueMessage(new TextWebSocketCommands.UpdateStatus());
QueueMessage(new TextWebSocketCommands.KeepAlive());
_connectWaitOnLogin.Set(); //Pre-Event
}
RaiseGotEvent(msg.Type, msg.Payload as JToken);
if (msg.Type == "READY")
_connectWaitOnLogin2.Set(); //Post-Event
break;
default:
RaiseOnDebugMessage("Unknown WebSocket operation ID: " + msg.Operation);
break;
}
}

protected override WebSocketMessage GetKeepAlive()
{
return new TextWebSocketCommands.KeepAlive();
}
}
}

+ 125
- 0
src/Discord.Net/DiscordVoiceWebSocket.cs View File

@@ -0,0 +1,125 @@
#if !DNXCORE50
using Discord.API.Models;
using Discord.Helpers;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace Discord
{
internal sealed partial class DiscordVoiceWebSocket : DiscordWebSocket
{
private const int ReadyTimeout = 2500; //Max time in milliseconds between connecting to Discord and receiving a READY event

private ManualResetEventSlim _connectWaitOnLogin, _connectWaitOnLogin2;
private UdpClient _udp;
private ConcurrentQueue<byte[]> _sendQueue;

public DiscordVoiceWebSocket(int interval)
: base(interval)
{
_connectWaitOnLogin = new ManualResetEventSlim(false);
_connectWaitOnLogin2 = new ManualResetEventSlim(false);

_udp = new UdpClient();
_sendQueue = new ConcurrentQueue<byte[]>();
}

protected override Task[] CreateTasks(CancellationToken cancelToken)
{
return new Task[]
{
Task.Factory.StartNew(ReceiveAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).Result,
Task.Factory.StartNew(SendAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).Result,
Task.Factory.StartNew(WatcherAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).Result
}.Concat(base.CreateTasks(cancelToken)).ToArray();
}
public async Task Login(string serverId, string userId, string sessionId)
{
var cancelToken = _disconnectToken.Token;

_connectWaitOnLogin.Reset();
_connectWaitOnLogin2.Reset();

string ip = await Http.Get("http://ipinfo.io/ip");

VoiceWebSocketCommands.Login msg = new VoiceWebSocketCommands.Login();
msg.Payload.Token = Http.Token;
msg.Payload.ServerId = serverId;
msg.Payload.UserId = userId;
msg.Payload.SessionId = sessionId;
await SendMessage(msg, cancelToken);

try
{
if (!_connectWaitOnLogin.Wait(ReadyTimeout, cancelToken)) //Waiting on READY message
throw new Exception("No reply from Discord server");
}
catch (OperationCanceledException)
{
throw new InvalidOperationException("Bad Token");
}
try { _connectWaitOnLogin2.Wait(cancelToken); } //Waiting on READY handler
catch (OperationCanceledException) { return; }

SetConnected();
}

private async Task ReceiveAsync()
{
var cancelToken = _disconnectToken.Token;

try
{
while (!cancelToken.IsCancellationRequested)
{
var result = await _udp.ReceiveAsync();
ProcessUdpMessage(result);
}
}
catch { }
finally { _disconnectToken.Cancel(); }
}
private async Task SendAsync()
{
var cancelToken = _disconnectToken.Token;
try
{
byte[] bytes;
while (!cancelToken.IsCancellationRequested)
{
while (_sendQueue.TryDequeue(out bytes))
await SendMessage(bytes, cancelToken);
await Task.Delay(_sendInterval);
}
}
catch { }
finally { _disconnectToken.Cancel(); }
}
private async Task WatcherAsync()
{
try
{
await Task.Delay(-1, _disconnectToken.Token);
}
catch (OperationCanceledException) { }
_udp.Close();
}

protected override void ProcessMessage(WebSocketMessage msg)
{
}
private void ProcessUdpMessage(UdpReceiveResult msg)
{
}

protected override WebSocketMessage GetKeepAlive()
{
return new VoiceWebSocketCommands.KeepAlive();
}
}
}
#endif

+ 3
- 21
src/Discord.Net/DiscordWebSocket.Events.cs View File

@@ -1,9 +1,8 @@
using Newtonsoft.Json.Linq;
using System;
using System;


namespace Discord namespace Discord
{ {
internal partial class DiscordWebSocket
internal abstract partial class DiscordWebSocket
{ {
public event EventHandler Connected; public event EventHandler Connected;
private void RaiseConnected() private void RaiseConnected()
@@ -19,25 +18,8 @@ namespace Discord
Disconnected(this, EventArgs.Empty); Disconnected(this, EventArgs.Empty);
} }


public event EventHandler<MessageEventArgs> GotEvent;
public sealed class MessageEventArgs : EventArgs
{
public readonly string Type;
public readonly JToken Event;
internal MessageEventArgs(string type, JToken data)
{
Type = type;
Event = data;
}
}
private void RaiseGotEvent(string type, JToken payload)
{
if (GotEvent != null)
GotEvent(this, new MessageEventArgs(type, payload));
}

public event EventHandler<DiscordClient.LogMessageEventArgs> OnDebugMessage; public event EventHandler<DiscordClient.LogMessageEventArgs> OnDebugMessage;
private void RaiseOnDebugMessage(string message)
protected void RaiseOnDebugMessage(string message)
{ {
if (OnDebugMessage != null) if (OnDebugMessage != null)
OnDebugMessage(this, new DiscordClient.LogMessageEventArgs(message)); OnDebugMessage(this, new DiscordClient.LogMessageEventArgs(message));


+ 31
- 72
src/Discord.Net/DiscordWebSocket.cs View File

@@ -1,5 +1,4 @@
using Discord.API.Models; using Discord.API.Models;
using Discord.Helpers;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
@@ -11,31 +10,29 @@ using System.Threading.Tasks;


namespace Discord namespace Discord
{ {
internal sealed partial class DiscordWebSocket : IDisposable
internal abstract partial class DiscordWebSocket : IDisposable
{ {
private const int ReceiveChunkSize = 4096; private const int ReceiveChunkSize = 4096;
private const int SendChunkSize = 4096; private const int SendChunkSize = 4096;
private const int ReadyTimeout = 2500; //Max time in milliseconds between connecting to Discord and receiving a READY event

protected volatile CancellationTokenSource _disconnectToken;
protected int _heartbeatInterval;
protected readonly int _sendInterval;


private volatile ClientWebSocket _webSocket; private volatile ClientWebSocket _webSocket;
private volatile CancellationTokenSource _disconnectToken;
private volatile Task _tasks; private volatile Task _tasks;
private ConcurrentQueue<byte[]> _sendQueue; private ConcurrentQueue<byte[]> _sendQueue;
private int _heartbeatInterval, _sendInterval;
private DateTime _lastHeartbeat; private DateTime _lastHeartbeat;
private ManualResetEventSlim _connectWaitOnLogin, _connectWaitOnLogin2;
private bool _isConnected; private bool _isConnected;


public DiscordWebSocket(int interval) public DiscordWebSocket(int interval)
{ {
_sendInterval = interval; _sendInterval = interval;
_connectWaitOnLogin = new ManualResetEventSlim(false);
_connectWaitOnLogin2 = new ManualResetEventSlim(false);

_sendQueue = new ConcurrentQueue<byte[]>(); _sendQueue = new ConcurrentQueue<byte[]>();
} }


public async Task ConnectAsync(string url, bool autoLogin)
public async Task ConnectAsync(string url)
{ {
await DisconnectAsync(); await DisconnectAsync();


@@ -46,9 +43,7 @@ namespace Discord
_webSocket.Options.KeepAliveInterval = TimeSpan.Zero; _webSocket.Options.KeepAliveInterval = TimeSpan.Zero;
await _webSocket.ConnectAsync(new Uri(url), cancelToken); await _webSocket.ConnectAsync(new Uri(url), cancelToken);


_tasks = Task.WhenAll(
await Task.Factory.StartNew(ReceiveAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default),
await Task.Factory.StartNew(SendAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default))
_tasks = Task.WhenAll(CreateTasks(cancelToken))
.ContinueWith(x => .ContinueWith(x =>
{ {
//Do not clean up until both tasks have ended //Do not clean up until both tasks have ended
@@ -71,48 +66,29 @@ namespace Discord


_tasks = null; _tasks = null;
}); });

if (autoLogin)
Login();
}
public void Login()
}
public async Task DisconnectAsync()
{ {
var cancelToken = _disconnectToken.Token;

_connectWaitOnLogin.Reset();
_connectWaitOnLogin2.Reset();

WebSocketCommands.Login msg = new WebSocketCommands.Login();
msg.Payload.Token = Http.Token;
msg.Payload.Properties["$os"] = "";
msg.Payload.Properties["$browser"] = "";
msg.Payload.Properties["$device"] = "Discord.Net";
msg.Payload.Properties["$referrer"] = "";
msg.Payload.Properties["$referring_domain"] = "";
SendMessage(msg, _disconnectToken.Token);

try
{
if (!_connectWaitOnLogin.Wait(ReadyTimeout, cancelToken)) //Waiting on READY message
throw new Exception("No reply from Discord server");
}
catch (OperationCanceledException)
if (_tasks != null)
{ {
throw new InvalidOperationException("Bad Token");
try { _disconnectToken.Cancel(); } catch (NullReferenceException) { }
try { await _tasks; } catch (NullReferenceException) { }
} }
try { _connectWaitOnLogin2.Wait(cancelToken); } //Waiting on READY handler
catch (OperationCanceledException) { return; }
}


protected void SetConnected()
{
_isConnected = true; _isConnected = true;
RaiseConnected(); RaiseConnected();
} }
public async Task DisconnectAsync()

protected virtual Task[] CreateTasks(CancellationToken cancelToken)
{ {
if (_tasks != null)
return new Task[]
{ {
try { _disconnectToken.Cancel(); } catch (NullReferenceException) { }
try { await _tasks; } catch (NullReferenceException) { }
}
Task.Factory.StartNew(ReceiveAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).Result,
Task.Factory.StartNew(SendAsync, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).Result
};
} }


private async Task ReceiveAsync() private async Task ReceiveAsync()
@@ -142,25 +118,7 @@ namespace Discord
while (!result.EndOfMessage); while (!result.EndOfMessage);


var msg = JsonConvert.DeserializeObject<WebSocketMessage>(builder.ToString()); var msg = JsonConvert.DeserializeObject<WebSocketMessage>(builder.ToString());
switch (msg.Operation)
{
case 0:
if (msg.Type == "READY")
{
var payload = (msg.Payload as JToken).ToObject<WebSocketEvents.Ready>();
_heartbeatInterval = payload.HeartbeatInterval;
QueueMessage(new WebSocketCommands.UpdateStatus());
QueueMessage(new WebSocketCommands.KeepAlive());
_connectWaitOnLogin.Set(); //Pre-Event
}
RaiseGotEvent(msg.Type, msg.Payload as JToken);
if (msg.Type == "READY")
_connectWaitOnLogin2.Set(); //Post-Event
break;
default:
RaiseOnDebugMessage("Unknown WebSocket operation ID: " + msg.Operation);
break;
}
ProcessMessage(msg);


builder.Clear(); builder.Clear();
} }
@@ -168,11 +126,10 @@ namespace Discord
catch { } catch { }
finally { _disconnectToken.Cancel(); } finally { _disconnectToken.Cancel(); }
} }

private async Task SendAsync() private async Task SendAsync()
{ {
var cancelToken = _disconnectToken.Token; var cancelToken = _disconnectToken.Token;
try
try
{ {
byte[] bytes; byte[] bytes;
while (_webSocket.State == WebSocketState.Open && !cancelToken.IsCancellationRequested) while (_webSocket.State == WebSocketState.Open && !cancelToken.IsCancellationRequested)
@@ -182,7 +139,7 @@ namespace Discord
DateTime now = DateTime.UtcNow; DateTime now = DateTime.UtcNow;
if ((now - _lastHeartbeat).TotalMilliseconds > _heartbeatInterval) if ((now - _lastHeartbeat).TotalMilliseconds > _heartbeatInterval)
{ {
await SendMessage(new WebSocketCommands.KeepAlive(), cancelToken);
await SendMessage(GetKeepAlive(), cancelToken);
_lastHeartbeat = now; _lastHeartbeat = now;
} }
} }
@@ -195,15 +152,17 @@ namespace Discord
finally { _disconnectToken.Cancel(); } finally { _disconnectToken.Cancel(); }
} }


private void QueueMessage(object message)
protected abstract void ProcessMessage(WebSocketMessage msg);
protected abstract WebSocketMessage GetKeepAlive();

protected void QueueMessage(object message)
{ {
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)); var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message));
_sendQueue.Enqueue(bytes); _sendQueue.Enqueue(bytes);
} }

private Task SendMessage(object message, CancellationToken cancelToken)
protected Task SendMessage(object message, CancellationToken cancelToken)
=> SendMessage(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)), cancelToken); => SendMessage(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)), cancelToken);
private async Task SendMessage(byte[] message, CancellationToken cancelToken)
protected async Task SendMessage(byte[] message, CancellationToken cancelToken)
{ {
var frameCount = (int)Math.Ceiling((double)message.Length / SendChunkSize); var frameCount = (int)Math.Ceiling((double)message.Length / SendChunkSize);




Loading…
Cancel
Save