| @@ -7,10 +7,26 @@ using Newtonsoft.Json; | |||||
| namespace Discord.API | 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 | //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 | public class Data | ||||
| { | { | ||||
| [JsonProperty("server_id")] | [JsonProperty("server_id")] | ||||
| @@ -25,9 +41,9 @@ namespace Discord.API | |||||
| public string Token; | 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 Data | ||||
| { | { | ||||
| public class SocketInfo | public class SocketInfo | ||||
| @@ -45,13 +61,13 @@ namespace Discord.API | |||||
| public SocketInfo SocketData = new SocketInfo(); | 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 | public class Data | ||||
| { | { | ||||
| [JsonProperty("delay")] | [JsonProperty("delay")] | ||||
| @@ -17,16 +17,6 @@ namespace Discord.Net.WebSockets | |||||
| { | { | ||||
| public partial class VoiceWebSocket : WebSocket | 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 int MaxOpusSize = 4000; | ||||
| private const string EncryptedMode = "xsalsa20_poly1305"; | private const string EncryptedMode = "xsalsa20_poly1305"; | ||||
| private const string UnencryptedMode = "plain"; | private const string UnencryptedMode = "plain"; | ||||
| @@ -114,12 +104,7 @@ namespace Discord.Net.WebSockets | |||||
| { | { | ||||
| _udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); | _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>(); | List<Task> tasks = new List<Task>(); | ||||
| if ((_audioConfig.Mode & AudioMode.Outgoing) != 0) | if ((_audioConfig.Mode & AudioMode.Outgoing) != 0) | ||||
| @@ -224,15 +209,10 @@ namespace Discord.Net.WebSockets | |||||
| if (packetLength != 70) | if (packetLength != 70) | ||||
| return; | return; | ||||
| int port = packet[68] | packet[69] << 8; | |||||
| string ip = Encoding.UTF8.GetString(packet, 4, 70 - 6).TrimEnd('\0'); | 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) | if ((_audioConfig.Mode & AudioMode.Incoming) == 0) | ||||
| return; | return; | ||||
| } | } | ||||
| @@ -441,10 +421,10 @@ namespace Discord.Net.WebSockets | |||||
| { | { | ||||
| await base.ProcessMessage(json).ConfigureAwait(false); | await base.ProcessMessage(json).ConfigureAwait(false); | ||||
| var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json); | var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json); | ||||
| var opCode = (OpCodes)msg.Operation; | |||||
| var opCode = (VoiceOpCodes)msg.Operation; | |||||
| switch (opCode) | switch (opCode) | ||||
| { | { | ||||
| case OpCodes.Ready: | |||||
| case VoiceOpCodes.Ready: | |||||
| { | { | ||||
| if (_state != (int)WebSocketState.Connected) | if (_state != (int)WebSocketState.Connected) | ||||
| { | { | ||||
| @@ -481,7 +461,7 @@ namespace Discord.Net.WebSockets | |||||
| } | } | ||||
| } | } | ||||
| break; | break; | ||||
| case OpCodes.Heartbeat: | |||||
| case VoiceOpCodes.Heartbeat: | |||||
| { | { | ||||
| long time = EpochTime.GetMilliseconds(); | long time = EpochTime.GetMilliseconds(); | ||||
| var payload = (long)msg.Payload; | var payload = (long)msg.Payload; | ||||
| @@ -489,7 +469,7 @@ namespace Discord.Net.WebSockets | |||||
| //TODO: Use this to estimate latency | //TODO: Use this to estimate latency | ||||
| } | } | ||||
| break; | break; | ||||
| case OpCodes.SessionDescription: | |||||
| case VoiceOpCodes.SessionDescription: | |||||
| { | { | ||||
| var payload = (msg.Payload as JToken).ToObject<JoinServerEvent>(_serializer); | var payload = (msg.Payload as JToken).ToObject<JoinServerEvent>(_serializer); | ||||
| _secretKey = payload.SecretKey; | _secretKey = payload.SecretKey; | ||||
| @@ -497,7 +477,7 @@ namespace Discord.Net.WebSockets | |||||
| EndConnect(); | EndConnect(); | ||||
| } | } | ||||
| break; | break; | ||||
| case OpCodes.Speaking: | |||||
| case VoiceOpCodes.Speaking: | |||||
| { | { | ||||
| var payload = (msg.Payload as JToken).ToObject<IsTalkingEvent>(_serializer); | var payload = (msg.Payload as JToken).ToObject<IsTalkingEvent>(_serializer); | ||||
| RaiseIsSpeaking(payload.UserId, payload.IsSpeaking); | RaiseIsSpeaking(payload.UserId, payload.IsSpeaking); | ||||
| @@ -519,19 +499,6 @@ namespace Discord.Net.WebSockets | |||||
| _sendBuffer.Clear(_cancelToken); | _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() | public void WaitForQueue() | ||||
| { | { | ||||
| _sendBuffer.Wait(_cancelToken); | _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 | public enum GatewayOpCodes : byte | ||||
| { | { | ||||
| /// <summary> Client <-- Server - Used to send most events. </summary> | |||||
| Dispatch = 0, | Dispatch = 0, | ||||
| /// <summary> Client <-> Server - Used to keep the connection alive and measure latency. </summary> | |||||
| Heartbeat = 1, | Heartbeat = 1, | ||||
| /// <summary> Client --> Server - Used to associate a connection with a token and specify configuration. </summary> | |||||
| Identify = 2, | Identify = 2, | ||||
| /// <summary> Client --> Server - Used to update client's status and current game id. </summary> | |||||
| StatusUpdate = 3, | StatusUpdate = 3, | ||||
| /// <summary> Client --> Server - Used to join a particular voice channel. </summary> | |||||
| VoiceStateUpdate = 4, | 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, | Resume = 6, | ||||
| /// <summary> Client <-- Server - Used to notify a client that they must reconnect to another gateway. </summary> | |||||
| Redirect = 7, | Redirect = 7, | ||||
| /// <summary> Client --> Server - Used to request all members that were withheld by large_threshold </summary> | |||||
| RequestGuildMembers = 8 | RequestGuildMembers = 8 | ||||
| } | } | ||||
| @@ -92,8 +101,6 @@ namespace Discord.API | |||||
| } | } | ||||
| } | } | ||||
| //Commands | |||||
| internal sealed class JoinVoiceCommand : WebSocketMessage<JoinVoiceCommand.Data> | internal sealed class JoinVoiceCommand : WebSocketMessage<JoinVoiceCommand.Data> | ||||
| { | { | ||||
| public JoinVoiceCommand() : base((int)GatewayOpCodes.VoiceStateUpdate) { } | public JoinVoiceCommand() : base((int)GatewayOpCodes.VoiceStateUpdate) { } | ||||
| @@ -112,7 +119,6 @@ namespace Discord.API | |||||
| } | } | ||||
| } | } | ||||
| //Events | |||||
| internal sealed class ResumeCommand : WebSocketMessage<ResumeCommand.Data> | internal sealed class ResumeCommand : WebSocketMessage<ResumeCommand.Data> | ||||
| { | { | ||||
| public ResumeCommand() : base((int)GatewayOpCodes.Resume) { } | public ResumeCommand() : base((int)GatewayOpCodes.Resume) { } | ||||
| @@ -109,7 +109,7 @@ namespace Discord.Net.WebSockets | |||||
| public void SendIdentify(string token) | public void SendIdentify(string token) | ||||
| { | { | ||||
| IdentifyCommand msg = new IdentifyCommand(); | |||||
| var msg = new IdentifyCommand(); | |||||
| msg.Payload.Token = token; | msg.Payload.Token = token; | ||||
| msg.Payload.Properties["$device"] = "Discord.Net"; | msg.Payload.Properties["$device"] = "Discord.Net"; | ||||
| if (_config.UseLargeThreshold) | if (_config.UseLargeThreshold) | ||||
| @@ -120,10 +120,10 @@ namespace Discord.Net.WebSockets | |||||
| public void SendResume() | 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() | public override void SendHeartbeat() | ||||
| @@ -133,31 +133,31 @@ namespace Discord.Net.WebSockets | |||||
| public void SendStatusUpdate(long? idleSince, int? gameId) | 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) | 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) | 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) | 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); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||