@@ -7,10 +7,26 @@ using Newtonsoft.Json; | |||
namespace Discord.API | |||
{ | |||
public enum VoiceOpCodes : byte | |||
{ | |||
/// <summary> Client --> Server - Used to associate a connection with a token. </summary> | |||
Identify = 0, | |||
/// <summary> Client --> Server - Used to specify configuration. </summary> | |||
SelectProtocol = 1, | |||
/// <summary> Client <-- Server - Used to notify that the voice connection was successful and informs the client of available protocols. </summary> | |||
Ready = 2, | |||
/// <summary> Client <-> Server - Used to keep the connection alive and measure latency. </summary> | |||
Heartbeat = 3, | |||
/// <summary> Client <-- Server - Used to provide an encryption key to the client. </summary> | |||
SessionDescription = 4, | |||
/// <summary> Client <-> Server - Used to inform that a certain user is speaking. </summary> | |||
Speaking = 5 | |||
} | |||
//Commands | |||
internal sealed class VoiceLoginCommand : WebSocketMessage<VoiceLoginCommand.Data> | |||
internal sealed class IdentifyCommand : WebSocketMessage<IdentifyCommand.Data> | |||
{ | |||
public VoiceLoginCommand() : base(0) { } | |||
public IdentifyCommand() : base((int)VoiceOpCodes.Identify) { } | |||
public class Data | |||
{ | |||
[JsonProperty("server_id")] | |||
@@ -25,9 +41,9 @@ namespace Discord.API | |||
public string Token; | |||
} | |||
} | |||
internal sealed class VoiceLogin2Command : WebSocketMessage<VoiceLogin2Command.Data> | |||
internal sealed class SelectProtocolCommand : WebSocketMessage<SelectProtocolCommand.Data> | |||
{ | |||
public VoiceLogin2Command() : base(1) { } | |||
public SelectProtocolCommand() : base((int)VoiceOpCodes.SelectProtocol) { } | |||
public class Data | |||
{ | |||
public class SocketInfo | |||
@@ -45,13 +61,13 @@ namespace Discord.API | |||
public SocketInfo SocketData = new SocketInfo(); | |||
} | |||
} | |||
internal sealed class VoiceKeepAliveCommand : WebSocketMessage<long> | |||
internal sealed class HeartbeatCommand : WebSocketMessage<long> | |||
{ | |||
public VoiceKeepAliveCommand() : base(3, EpochTime.GetMilliseconds()) { } | |||
public HeartbeatCommand() : base((int)VoiceOpCodes.Heartbeat, EpochTime.GetMilliseconds()) { } | |||
} | |||
internal sealed class IsTalkingCommand : WebSocketMessage<IsTalkingCommand.Data> | |||
internal sealed class SpeakingCommand : WebSocketMessage<SpeakingCommand.Data> | |||
{ | |||
public IsTalkingCommand() : base(5) { } | |||
public SpeakingCommand() : base((int)VoiceOpCodes.Speaking) { } | |||
public class Data | |||
{ | |||
[JsonProperty("delay")] | |||
@@ -17,16 +17,6 @@ namespace Discord.Net.WebSockets | |||
{ | |||
public partial class VoiceWebSocket : WebSocket | |||
{ | |||
public enum OpCodes : byte | |||
{ | |||
Identify = 0, | |||
SelectProtocol = 1, | |||
Ready = 2, | |||
Heartbeat = 3, | |||
SessionDescription = 4, | |||
Speaking = 5 | |||
} | |||
private const int MaxOpusSize = 4000; | |||
private const string EncryptedMode = "xsalsa20_poly1305"; | |||
private const string UnencryptedMode = "plain"; | |||
@@ -114,12 +104,7 @@ namespace Discord.Net.WebSockets | |||
{ | |||
_udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); | |||
VoiceLoginCommand msg = new VoiceLoginCommand(); | |||
msg.Payload.ServerId = _serverId.Value; | |||
msg.Payload.SessionId = _sessionId; | |||
msg.Payload.Token = _token; | |||
msg.Payload.UserId = _userId.Value; | |||
QueueMessage(msg); | |||
SendIdentify(); | |||
List<Task> tasks = new List<Task>(); | |||
if ((_audioConfig.Mode & AudioMode.Outgoing) != 0) | |||
@@ -224,15 +209,10 @@ namespace Discord.Net.WebSockets | |||
if (packetLength != 70) | |||
return; | |||
int port = packet[68] | packet[69] << 8; | |||
string ip = Encoding.UTF8.GetString(packet, 4, 70 - 6).TrimEnd('\0'); | |||
int port = packet[68] | packet[69] << 8; | |||
var login2 = new VoiceLogin2Command(); | |||
login2.Payload.Protocol = "udp"; | |||
login2.Payload.SocketData.Address = ip; | |||
login2.Payload.SocketData.Mode = _encryptionMode; | |||
login2.Payload.SocketData.Port = port; | |||
QueueMessage(login2); | |||
SendSelectProtocol(ip, port); | |||
if ((_audioConfig.Mode & AudioMode.Incoming) == 0) | |||
return; | |||
} | |||
@@ -441,10 +421,10 @@ namespace Discord.Net.WebSockets | |||
{ | |||
await base.ProcessMessage(json).ConfigureAwait(false); | |||
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json); | |||
var opCode = (OpCodes)msg.Operation; | |||
var opCode = (VoiceOpCodes)msg.Operation; | |||
switch (opCode) | |||
{ | |||
case OpCodes.Ready: | |||
case VoiceOpCodes.Ready: | |||
{ | |||
if (_state != (int)WebSocketState.Connected) | |||
{ | |||
@@ -481,7 +461,7 @@ namespace Discord.Net.WebSockets | |||
} | |||
} | |||
break; | |||
case OpCodes.Heartbeat: | |||
case VoiceOpCodes.Heartbeat: | |||
{ | |||
long time = EpochTime.GetMilliseconds(); | |||
var payload = (long)msg.Payload; | |||
@@ -489,7 +469,7 @@ namespace Discord.Net.WebSockets | |||
//TODO: Use this to estimate latency | |||
} | |||
break; | |||
case OpCodes.SessionDescription: | |||
case VoiceOpCodes.SessionDescription: | |||
{ | |||
var payload = (msg.Payload as JToken).ToObject<JoinServerEvent>(_serializer); | |||
_secretKey = payload.SecretKey; | |||
@@ -497,7 +477,7 @@ namespace Discord.Net.WebSockets | |||
EndConnect(); | |||
} | |||
break; | |||
case OpCodes.Speaking: | |||
case VoiceOpCodes.Speaking: | |||
{ | |||
var payload = (msg.Payload as JToken).ToObject<IsTalkingEvent>(_serializer); | |||
RaiseIsSpeaking(payload.UserId, payload.IsSpeaking); | |||
@@ -519,19 +499,6 @@ namespace Discord.Net.WebSockets | |||
_sendBuffer.Clear(_cancelToken); | |||
} | |||
private void SendIsTalking(bool value) | |||
{ | |||
var isTalking = new IsTalkingCommand(); | |||
isTalking.Payload.IsSpeaking = value; | |||
isTalking.Payload.Delay = 0; | |||
QueueMessage(isTalking); | |||
} | |||
public override void SendHeartbeat() | |||
{ | |||
QueueMessage(new VoiceKeepAliveCommand()); | |||
} | |||
public void WaitForQueue() | |||
{ | |||
_sendBuffer.Wait(_cancelToken); | |||
@@ -551,5 +518,38 @@ namespace Discord.Net.WebSockets | |||
} | |||
}); | |||
} | |||
public void SendIdentify() | |||
{ | |||
var msg = new IdentifyCommand(); | |||
msg.Payload.ServerId = _serverId.Value; | |||
msg.Payload.SessionId = _sessionId; | |||
msg.Payload.Token = _token; | |||
msg.Payload.UserId = _userId.Value; | |||
QueueMessage(msg); | |||
} | |||
public void SendSelectProtocol(string externalIp, int externalPort) | |||
{ | |||
var msg = new SelectProtocolCommand(); | |||
msg.Payload.Protocol = "udp"; | |||
msg.Payload.SocketData.Address = externalIp; | |||
msg.Payload.SocketData.Mode = _encryptionMode; | |||
msg.Payload.SocketData.Port = externalPort; | |||
QueueMessage(msg); | |||
} | |||
public void SendIsTalking(bool value) | |||
{ | |||
var isTalking = new SpeakingCommand(); | |||
isTalking.Payload.IsSpeaking = value; | |||
isTalking.Payload.Delay = 0; | |||
QueueMessage(isTalking); | |||
} | |||
public override void SendHeartbeat() | |||
{ | |||
QueueMessage(new HeartbeatCommand()); | |||
} | |||
} | |||
} |
@@ -11,14 +11,23 @@ namespace Discord.API | |||
{ | |||
public enum GatewayOpCodes : byte | |||
{ | |||
/// <summary> Client <-- Server - Used to send most events. </summary> | |||
Dispatch = 0, | |||
/// <summary> Client <-> Server - Used to keep the connection alive and measure latency. </summary> | |||
Heartbeat = 1, | |||
/// <summary> Client --> Server - Used to associate a connection with a token and specify configuration. </summary> | |||
Identify = 2, | |||
/// <summary> Client --> Server - Used to update client's status and current game id. </summary> | |||
StatusUpdate = 3, | |||
/// <summary> Client --> Server - Used to join a particular voice channel. </summary> | |||
VoiceStateUpdate = 4, | |||
//VoiceServerPing = 5, (Unused?) | |||
/// <summary> Client --> Server - Used to ensure the server's voice server is alive. Only send this if voice connection fails or suddenly drops. </summary> | |||
VoiceServerPing = 5, | |||
/// <summary> Client --> Server - Used to resume a connection after a redirect occurs. </summary> | |||
Resume = 6, | |||
/// <summary> Client <-- Server - Used to notify a client that they must reconnect to another gateway. </summary> | |||
Redirect = 7, | |||
/// <summary> Client --> Server - Used to request all members that were withheld by large_threshold </summary> | |||
RequestGuildMembers = 8 | |||
} | |||
@@ -92,8 +101,6 @@ namespace Discord.API | |||
} | |||
} | |||
//Commands | |||
internal sealed class JoinVoiceCommand : WebSocketMessage<JoinVoiceCommand.Data> | |||
{ | |||
public JoinVoiceCommand() : base((int)GatewayOpCodes.VoiceStateUpdate) { } | |||
@@ -112,7 +119,6 @@ namespace Discord.API | |||
} | |||
} | |||
//Events | |||
internal sealed class ResumeCommand : WebSocketMessage<ResumeCommand.Data> | |||
{ | |||
public ResumeCommand() : base((int)GatewayOpCodes.Resume) { } | |||
@@ -109,7 +109,7 @@ namespace Discord.Net.WebSockets | |||
public void SendIdentify(string token) | |||
{ | |||
IdentifyCommand msg = new IdentifyCommand(); | |||
var msg = new IdentifyCommand(); | |||
msg.Payload.Token = token; | |||
msg.Payload.Properties["$device"] = "Discord.Net"; | |||
if (_config.UseLargeThreshold) | |||
@@ -120,10 +120,10 @@ namespace Discord.Net.WebSockets | |||
public void SendResume() | |||
{ | |||
var resumeMsg = new ResumeCommand(); | |||
resumeMsg.Payload.SessionId = _sessionId; | |||
resumeMsg.Payload.Sequence = _lastSeq; | |||
QueueMessage(resumeMsg); | |||
var msg = new ResumeCommand(); | |||
msg.Payload.SessionId = _sessionId; | |||
msg.Payload.Sequence = _lastSeq; | |||
QueueMessage(msg); | |||
} | |||
public override void SendHeartbeat() | |||
@@ -133,31 +133,31 @@ namespace Discord.Net.WebSockets | |||
public void SendStatusUpdate(long? idleSince, int? gameId) | |||
{ | |||
var updateStatus = new StatusUpdateCommand(); | |||
updateStatus.Payload.IdleSince = idleSince; | |||
updateStatus.Payload.GameId = gameId; | |||
QueueMessage(updateStatus); | |||
var msg = new StatusUpdateCommand(); | |||
msg.Payload.IdleSince = idleSince; | |||
msg.Payload.GameId = gameId; | |||
QueueMessage(msg); | |||
} | |||
public void SendJoinVoice(long serverId, long channelId) | |||
{ | |||
var joinVoice = new JoinVoiceCommand(); | |||
joinVoice.Payload.ServerId = serverId; | |||
joinVoice.Payload.ChannelId = channelId; | |||
QueueMessage(joinVoice); | |||
var msg = new JoinVoiceCommand(); | |||
msg.Payload.ServerId = serverId; | |||
msg.Payload.ChannelId = channelId; | |||
QueueMessage(msg); | |||
} | |||
public void SendLeaveVoice(long serverId) | |||
{ | |||
var leaveVoice = new JoinVoiceCommand(); | |||
leaveVoice.Payload.ServerId = serverId; | |||
QueueMessage(leaveVoice); | |||
var msg = new JoinVoiceCommand(); | |||
msg.Payload.ServerId = serverId; | |||
QueueMessage(msg); | |||
} | |||
public void SendRequestUsers(long serverId, string query = "", int limit = 0) | |||
{ | |||
var getOfflineUsers = new GetUsersCommand(); | |||
getOfflineUsers.Payload.ServerId = serverId; | |||
QueueMessage(getOfflineUsers); | |||
var msg = new GetUsersCommand(); | |||
msg.Payload.ServerId = serverId; | |||
QueueMessage(msg); | |||
} | |||
} | |||
} |