Started adding support for voice
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
"sdk": {
|
||||
"version": "1.0.0-beta6",
|
||||
"architecture": "x64",
|
||||
"runtime": "coreclr"
|
||||
"runtime": "clr"
|
||||
}
|
||||
}
|
||||
@@ -58,11 +58,17 @@
|
||||
<Compile Include="..\Discord.Net\API\Models\Common.cs">
|
||||
<Link>API\Models\Common.cs</Link>
|
||||
</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 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 Include="..\Discord.Net\Channel.cs">
|
||||
<Link>Channel.cs</Link>
|
||||
@@ -79,6 +85,15 @@
|
||||
<Compile Include="..\Discord.Net\DiscordClientConfig.cs">
|
||||
<Link>DiscordClientConfig.cs</Link>
|
||||
</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">
|
||||
<Link>DiscordWebSocket.cs</Link>
|
||||
</Compile>
|
||||
@@ -91,6 +106,9 @@
|
||||
<Compile Include="..\Discord.Net\Helpers\Http.cs">
|
||||
<Link>Helpers\Http.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\HttpException.cs">
|
||||
<Link>HttpException.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Discord.Net\Invite.cs">
|
||||
<Link>Invite.cs</Link>
|
||||
</Compile>
|
||||
@@ -115,7 +133,6 @@
|
||||
<Compile Include="..\Discord.Net\User.cs">
|
||||
<Link>User.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="HttpException.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Discord.API.Models
|
||||
{
|
||||
internal static class WebSocketCommands
|
||||
internal static class TextWebSocketCommands
|
||||
{
|
||||
public sealed class KeepAlive : WebSocketMessage<int>
|
||||
{
|
||||
@@ -3,11 +3,10 @@
|
||||
#pragma warning disable CS0169
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
|
||||
namespace Discord.API.Models
|
||||
{
|
||||
internal static class WebSocketEvents
|
||||
internal static class TextWebSocketEvents
|
||||
{
|
||||
public sealed class Ready
|
||||
{
|
||||
53
src/Discord.Net/API/Models/VoiceWebSocketCommands.cs
Normal file
53
src/Discord.Net/API/Models/VoiceWebSocketCommands.cs
Normal 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
src/Discord.Net/API/Models/VoiceWebSocketEvents.cs
Normal file
12
src/Discord.Net/API/Models/VoiceWebSocketEvents.cs
Normal 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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,12 @@ namespace Discord
|
||||
if (DebugMessage != null)
|
||||
DebugMessage(this, new LogMessageEventArgs(message));
|
||||
}
|
||||
public event EventHandler<LogMessageEventArgs> VoiceDebugMessage;
|
||||
private void RaiseOnVoiceDebugMessage(string message)
|
||||
{
|
||||
if (VoiceDebugMessage != null)
|
||||
VoiceDebugMessage(this, new LogMessageEventArgs(message));
|
||||
}
|
||||
|
||||
//General
|
||||
public event EventHandler Connected;
|
||||
|
||||
@@ -17,16 +17,22 @@ namespace Discord
|
||||
/// <summary> Provides a connection to the DiscordApp service. </summary>
|
||||
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 MatchEvaluator _userRegexEvaluator, _channelRegexEvaluator;
|
||||
private readonly JsonSerializer _serializer;
|
||||
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>
|
||||
public User User { get; private set; }
|
||||
/// <summary> Returns the id of the current logged in user. </summary>
|
||||
@@ -276,7 +282,7 @@ namespace Discord
|
||||
user => { }
|
||||
);
|
||||
|
||||
_webSocket = new DiscordWebSocket(_config.WebSocketInterval);
|
||||
_webSocket = new DiscordTextWebSocket(_config.WebSocketInterval);
|
||||
_webSocket.Connected += (s, e) => RaiseConnected();
|
||||
_webSocket.Disconnected += async (s, e) =>
|
||||
{
|
||||
@@ -287,7 +293,8 @@ namespace Discord
|
||||
try
|
||||
{
|
||||
await Task.Delay(_config.ReconnectDelay);
|
||||
await _webSocket.ConnectAsync(Endpoints.WebSocket_Hub, true);
|
||||
await _webSocket.ConnectAsync(Endpoints.WebSocket_Hub);
|
||||
await _webSocket.Login();
|
||||
break;
|
||||
}
|
||||
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) =>
|
||||
{
|
||||
switch (e.Type)
|
||||
@@ -305,7 +341,7 @@ namespace Discord
|
||||
//Global
|
||||
case "READY": //Resync
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.Ready>(_serializer);
|
||||
var data = e.Event.ToObject<TextWebSocketEvents.Ready>(_serializer);
|
||||
|
||||
_servers.Clear();
|
||||
_channels.Clear();
|
||||
@@ -324,21 +360,21 @@ namespace Discord
|
||||
//Servers
|
||||
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);
|
||||
try { RaiseServerCreated(server); } catch { }
|
||||
}
|
||||
break;
|
||||
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);
|
||||
try { RaiseServerUpdated(server); } catch { }
|
||||
}
|
||||
break;
|
||||
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);
|
||||
if (server != null)
|
||||
try { RaiseServerDestroyed(server); } catch { }
|
||||
@@ -348,21 +384,21 @@ namespace Discord
|
||||
//Channels
|
||||
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);
|
||||
try { RaiseChannelCreated(channel); } catch { }
|
||||
}
|
||||
break;
|
||||
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);
|
||||
try { RaiseChannelUpdated(channel); } catch { }
|
||||
}
|
||||
break;
|
||||
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);
|
||||
if (channel != null)
|
||||
try { RaiseChannelDestroyed(channel); } catch { }
|
||||
@@ -372,7 +408,7 @@ namespace Discord
|
||||
//Members
|
||||
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 server = _servers[data.ServerId];
|
||||
if (server != null)
|
||||
@@ -384,7 +420,7 @@ namespace Discord
|
||||
break;
|
||||
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 server = _servers[data.ServerId];
|
||||
if (server != null)
|
||||
@@ -396,7 +432,7 @@ namespace Discord
|
||||
break;
|
||||
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];
|
||||
if (server != null)
|
||||
{
|
||||
@@ -410,21 +446,21 @@ namespace Discord
|
||||
//Roles
|
||||
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);
|
||||
try { RaiseRoleCreated(role); } catch { }
|
||||
}
|
||||
break;
|
||||
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);
|
||||
try { RaiseRoleUpdated(role); } catch { }
|
||||
}
|
||||
break;
|
||||
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);
|
||||
if (role != null)
|
||||
try { RaiseRoleDeleted(role); } catch { }
|
||||
@@ -434,7 +470,7 @@ namespace Discord
|
||||
//Bans
|
||||
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 server = _servers[data.ServerId];
|
||||
try { RaiseBanAdded(user, server); } catch { }
|
||||
@@ -442,7 +478,7 @@ namespace Discord
|
||||
break;
|
||||
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 server = _servers[data.ServerId];
|
||||
if (server != null && server.RemoveBan(user.Id))
|
||||
@@ -455,7 +491,7 @@ namespace Discord
|
||||
//Messages
|
||||
case "MESSAGE_CREATE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.MessageCreate>(_serializer);
|
||||
var data = e.Event.ToObject<TextWebSocketEvents.MessageCreate>(_serializer);
|
||||
Message msg = null;
|
||||
bool wasLocal = _config.UseMessageQueue && data.Author.Id == UserId && data.Nonce != null;
|
||||
if (wasLocal)
|
||||
@@ -478,14 +514,14 @@ namespace Discord
|
||||
break;
|
||||
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);
|
||||
try { RaiseMessageUpdated(msg); } catch { }
|
||||
}
|
||||
break;
|
||||
case "MESSAGE_DELETE":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.MessageDelete>(_serializer);
|
||||
var data = e.Event.ToObject<TextWebSocketEvents.MessageDelete>(_serializer);
|
||||
var msg = GetMessage(data.MessageId);
|
||||
if (msg != null)
|
||||
{
|
||||
@@ -496,7 +532,7 @@ namespace Discord
|
||||
break;
|
||||
case "MESSAGE_ACK":
|
||||
{
|
||||
var data = e.Event.ToObject<WebSocketEvents.MessageAck>(_serializer);
|
||||
var data = e.Event.ToObject<TextWebSocketEvents.MessageAck>(_serializer);
|
||||
var msg = GetMessage(data.MessageId);
|
||||
if (msg != null)
|
||||
try { RaiseMessageRead(msg); } catch { }
|
||||
@@ -506,7 +542,7 @@ namespace Discord
|
||||
//Statuses
|
||||
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 server = _servers[data.ServerId];
|
||||
if (server != null)
|
||||
@@ -518,7 +554,7 @@ namespace Discord
|
||||
break;
|
||||
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];
|
||||
if (server != null)
|
||||
{
|
||||
@@ -530,7 +566,7 @@ namespace Discord
|
||||
break;
|
||||
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 user = _users[data.UserId];
|
||||
if (user != null)
|
||||
@@ -545,7 +581,7 @@ namespace Discord
|
||||
//Voice
|
||||
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];
|
||||
try { RaiseVoiceServerUpdated(server, data.Endpoint); } catch { }
|
||||
}
|
||||
@@ -554,7 +590,7 @@ namespace Discord
|
||||
//Settings
|
||||
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);
|
||||
try { RaiseUserUpdated(user); } catch { }
|
||||
}
|
||||
@@ -571,7 +607,6 @@ namespace Discord
|
||||
break;
|
||||
}
|
||||
};
|
||||
_webSocket.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Message);
|
||||
}
|
||||
|
||||
private async Task SendAsync()
|
||||
@@ -822,8 +857,9 @@ namespace Discord
|
||||
{
|
||||
try
|
||||
{
|
||||
await _webSocket.ConnectAsync(Endpoints.WebSocket_Hub);
|
||||
Http.Token = token;
|
||||
await _webSocket.ConnectAsync(Endpoints.WebSocket_Hub, true);
|
||||
await _webSocket.Login();
|
||||
success = true;
|
||||
}
|
||||
catch (InvalidOperationException) //Bad Token
|
||||
@@ -835,27 +871,27 @@ namespace Discord
|
||||
if (!success && password != null) //Email/Password login
|
||||
{
|
||||
//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);
|
||||
await socketTask;
|
||||
|
||||
//Wait for websocket to finish connecting, then send token
|
||||
token = response.Token;
|
||||
Http.Token = token;
|
||||
_webSocket.Login();
|
||||
await _webSocket.Login();
|
||||
success = true;
|
||||
}
|
||||
if (!success && password == null) //Anonymous login
|
||||
{
|
||||
//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);
|
||||
await socketTask;
|
||||
|
||||
//Wait for websocket to finish connecting, then send token
|
||||
token = response.Token;
|
||||
Http.Token = token;
|
||||
_webSocket.Login();
|
||||
await _webSocket.Login();
|
||||
success = true;
|
||||
}
|
||||
if (success)
|
||||
@@ -868,6 +904,9 @@ namespace Discord
|
||||
_tasks = _tasks.ContinueWith(async x =>
|
||||
{
|
||||
await _webSocket.DisconnectAsync();
|
||||
#if !DNXCORE50
|
||||
await _voiceWebSocket.DisconnectAsync();
|
||||
#endif
|
||||
|
||||
//Clear send queue
|
||||
Message ignored;
|
||||
|
||||
25
src/Discord.Net/DiscordTextWebSocket.Events.cs
Normal file
25
src/Discord.Net/DiscordTextWebSocket.Events.cs
Normal 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
src/Discord.Net/DiscordTextWebSocket.cs
Normal file
82
src/Discord.Net/DiscordTextWebSocket.cs
Normal 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
src/Discord.Net/DiscordVoiceWebSocket.cs
Normal file
125
src/Discord.Net/DiscordVoiceWebSocket.cs
Normal 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
|
||||
@@ -1,9 +1,8 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal partial class DiscordWebSocket
|
||||
internal abstract partial class DiscordWebSocket
|
||||
{
|
||||
public event EventHandler Connected;
|
||||
private void RaiseConnected()
|
||||
@@ -19,25 +18,8 @@ namespace Discord
|
||||
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;
|
||||
private void RaiseOnDebugMessage(string message)
|
||||
protected void RaiseOnDebugMessage(string message)
|
||||
{
|
||||
if (OnDebugMessage != null)
|
||||
OnDebugMessage(this, new DiscordClient.LogMessageEventArgs(message));
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Discord.API.Models;
|
||||
using Discord.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
@@ -11,31 +10,29 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal sealed partial class DiscordWebSocket : IDisposable
|
||||
internal abstract partial class DiscordWebSocket : IDisposable
|
||||
{
|
||||
private const int ReceiveChunkSize = 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 CancellationTokenSource _disconnectToken;
|
||||
private volatile Task _tasks;
|
||||
private ConcurrentQueue<byte[]> _sendQueue;
|
||||
private int _heartbeatInterval, _sendInterval;
|
||||
private DateTime _lastHeartbeat;
|
||||
private ManualResetEventSlim _connectWaitOnLogin, _connectWaitOnLogin2;
|
||||
private bool _isConnected;
|
||||
|
||||
public DiscordWebSocket(int interval)
|
||||
{
|
||||
_sendInterval = interval;
|
||||
_connectWaitOnLogin = new ManualResetEventSlim(false);
|
||||
_connectWaitOnLogin2 = new ManualResetEventSlim(false);
|
||||
|
||||
|
||||
_sendQueue = new ConcurrentQueue<byte[]>();
|
||||
}
|
||||
|
||||
public async Task ConnectAsync(string url, bool autoLogin)
|
||||
public async Task ConnectAsync(string url)
|
||||
{
|
||||
await DisconnectAsync();
|
||||
|
||||
@@ -46,9 +43,7 @@ namespace Discord
|
||||
_webSocket.Options.KeepAliveInterval = TimeSpan.Zero;
|
||||
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 =>
|
||||
{
|
||||
//Do not clean up until both tasks have ended
|
||||
@@ -71,40 +66,6 @@ namespace Discord
|
||||
|
||||
_tasks = null;
|
||||
});
|
||||
|
||||
if (autoLogin)
|
||||
Login();
|
||||
}
|
||||
public void Login()
|
||||
{
|
||||
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)
|
||||
{
|
||||
throw new InvalidOperationException("Bad Token");
|
||||
}
|
||||
try { _connectWaitOnLogin2.Wait(cancelToken); } //Waiting on READY handler
|
||||
catch (OperationCanceledException) { return; }
|
||||
|
||||
_isConnected = true;
|
||||
RaiseConnected();
|
||||
}
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
@@ -115,6 +76,21 @@ namespace Discord
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetConnected()
|
||||
{
|
||||
_isConnected = true;
|
||||
RaiseConnected();
|
||||
}
|
||||
|
||||
protected virtual 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
|
||||
};
|
||||
}
|
||||
|
||||
private async Task ReceiveAsync()
|
||||
{
|
||||
var cancelToken = _disconnectToken.Token;
|
||||
@@ -142,25 +118,7 @@ namespace Discord
|
||||
while (!result.EndOfMessage);
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -168,11 +126,10 @@ namespace Discord
|
||||
catch { }
|
||||
finally { _disconnectToken.Cancel(); }
|
||||
}
|
||||
|
||||
private async Task SendAsync()
|
||||
{
|
||||
var cancelToken = _disconnectToken.Token;
|
||||
try
|
||||
try
|
||||
{
|
||||
byte[] bytes;
|
||||
while (_webSocket.State == WebSocketState.Open && !cancelToken.IsCancellationRequested)
|
||||
@@ -182,7 +139,7 @@ namespace Discord
|
||||
DateTime now = DateTime.UtcNow;
|
||||
if ((now - _lastHeartbeat).TotalMilliseconds > _heartbeatInterval)
|
||||
{
|
||||
await SendMessage(new WebSocketCommands.KeepAlive(), cancelToken);
|
||||
await SendMessage(GetKeepAlive(), cancelToken);
|
||||
_lastHeartbeat = now;
|
||||
}
|
||||
}
|
||||
@@ -195,15 +152,17 @@ namespace Discord
|
||||
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));
|
||||
_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);
|
||||
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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user