| @@ -0,0 +1,83 @@ | |||||
| using System; | |||||
| using System.Collections.Concurrent; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Sessions | |||||
| { | |||||
| public class SessionsService : IService | |||||
| { | |||||
| private static readonly DualChannelPermissions _ownerPerm = new DualChannelPermissions() { ReadMessages = true, ManageChannel = true }; | |||||
| private static readonly DualChannelPermissions _memberPerm = new DualChannelPermissions() { ReadMessages = true }; | |||||
| private static readonly DualChannelPermissions _everyonePerm = new DualChannelPermissions() { ReadMessages = false }; | |||||
| private DiscordClient _client; | |||||
| public void Install(DiscordClient client) | |||||
| { | |||||
| _client = client; | |||||
| } | |||||
| public IEnumerable<Channel> GetSessions(Server server) | |||||
| => server.TextChannels.Where(x => x.Name != "" && x.Name[0] == '!'); | |||||
| public async Task<Channel> CreateSession(Server server, string name, bool includeVoice, User owner) | |||||
| { | |||||
| name = '!' + name; | |||||
| Channel textChannel = await _client.CreateChannel(server, name, ChannelType.Text); | |||||
| Channel voiceChannel = includeVoice ? await _client.CreateChannel(server, name, ChannelType.Voice) : null; | |||||
| //Take away read from everyone | |||||
| await _client.SetChannelPermissions(textChannel, server.EveryoneRole, _everyonePerm); | |||||
| await _client.SetChannelPermissions(textChannel, owner, _ownerPerm); | |||||
| return textChannel; | |||||
| } | |||||
| public async Task DestroySession(Channel channel) | |||||
| { | |||||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||||
| CheckSession(channel); | |||||
| await _client.DeleteChannel(channel); | |||||
| } | |||||
| public Task JoinSession(Channel channel, User user) | |||||
| { | |||||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||||
| if (user == null) throw new ArgumentNullException(nameof(user)); | |||||
| CheckSession(channel); | |||||
| return _client.SetChannelPermissions(channel, user, _memberPerm); | |||||
| } | |||||
| public async Task LeaveSession(Channel channel, User user) | |||||
| { | |||||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | |||||
| if (user == null) throw new ArgumentNullException(nameof(user)); | |||||
| CheckSession(channel); | |||||
| if (IsOwner(channel, user)) | |||||
| await DestroySession(channel); | |||||
| else | |||||
| await _client.RemoveChannelPermissions(channel, user); | |||||
| } | |||||
| private bool IsSession(Channel channel) | |||||
| => channel.Name == "" && channel.Name[0] == '!'; | |||||
| private void CheckSession(Channel channel) | |||||
| { | |||||
| if (!IsSession(channel)) | |||||
| throw new InvalidOperationException("The provided channel is not a session."); | |||||
| } | |||||
| private bool IsOwner(Channel channel, User user) | |||||
| => _client.GetChannelPermissions(channel, user).ManageMessages == true; | |||||
| /*private IEnumerable<string> GetPermissionUsers(Channel channel) | |||||
| { | |||||
| return channel.PermissionOverwrites | |||||
| .Where(x => x.TargetType == PermissionTarget.User && x.Allow.Text_ReadMessages) | |||||
| .Select(x => x.TargetId); | |||||
| }*/ | |||||
| } | |||||
| } | |||||
| @@ -23,24 +23,6 @@ namespace Discord.API | |||||
| } | } | ||||
| } | } | ||||
| /*public class GetIceResponse | |||||
| { | |||||
| [JsonProperty("ttl")] | |||||
| public string TTL; | |||||
| [JsonProperty("servers")] | |||||
| public ServerData[] Servers; | |||||
| public sealed class ServerData | |||||
| { | |||||
| [JsonProperty("url")] | |||||
| public string URL; | |||||
| [JsonProperty("username")] | |||||
| public string Username; | |||||
| [JsonProperty("credential")] | |||||
| public string Credential; | |||||
| } | |||||
| }*/ | |||||
| //Commands | //Commands | ||||
| internal sealed class JoinVoiceCommand : WebSocketMessage<JoinVoiceCommand.Data> | internal sealed class JoinVoiceCommand : WebSocketMessage<JoinVoiceCommand.Data> | ||||
| { | { | ||||
| @@ -110,9 +92,9 @@ namespace Discord.API | |||||
| public SocketInfo SocketData = new SocketInfo(); | public SocketInfo SocketData = new SocketInfo(); | ||||
| } | } | ||||
| } | } | ||||
| internal sealed class VoiceKeepAliveCommand : WebSocketMessage<object> | |||||
| internal sealed class VoiceKeepAliveCommand : WebSocketMessage<ulong> | |||||
| { | { | ||||
| public VoiceKeepAliveCommand() : base(3, null) { } | |||||
| public VoiceKeepAliveCommand() : base(3, EpochTime.GetMilliseconds()) { } | |||||
| } | } | ||||
| internal sealed class IsTalkingCommand : WebSocketMessage<IsTalkingCommand.Data> | internal sealed class IsTalkingCommand : WebSocketMessage<IsTalkingCommand.Data> | ||||
| { | { | ||||
| @@ -48,7 +48,7 @@ namespace Discord | |||||
| public async Task<IDiscordVoiceClient> JoinVoiceServer(Channel channel) | public async Task<IDiscordVoiceClient> JoinVoiceServer(Channel channel) | ||||
| { | { | ||||
| if (channel == null) throw new ArgumentNullException(nameof(channel)); | if (channel == null) throw new ArgumentNullException(nameof(channel)); | ||||
| CheckReady(); //checkVoice is done inside the voice client | |||||
| CheckReady(true); //checkVoice is done inside the voice client | |||||
| var client = await CreateVoiceClient(channel.Server).ConfigureAwait(false); | var client = await CreateVoiceClient(channel.Server).ConfigureAwait(false); | ||||
| await client.JoinChannel(channel.Id).ConfigureAwait(false); | await client.JoinChannel(channel.Id).ConfigureAwait(false); | ||||
| @@ -282,7 +282,7 @@ namespace Discord | |||||
| throw new InvalidOperationException("The client is connecting."); | throw new InvalidOperationException("The client is connecting."); | ||||
| } | } | ||||
| if (checkVoice && !_config.EnableVoice) | |||||
| if (checkVoice && _config.VoiceMode == DiscordVoiceMode.Disabled) | |||||
| throw new InvalidOperationException("Voice is not enabled for this client."); | throw new InvalidOperationException("Voice is not enabled for this client."); | ||||
| } | } | ||||
| protected void RaiseEvent(string name, Action action) | protected void RaiseEvent(string name, Action action) | ||||
| @@ -36,12 +36,14 @@ namespace Discord.Net.WebSockets | |||||
| private ushort _sequence; | private ushort _sequence; | ||||
| private long? _serverId, _channelId, _userId; | private long? _serverId, _channelId, _userId; | ||||
| private string _sessionId, _token, _encryptionMode; | private string _sessionId, _token, _encryptionMode; | ||||
| private ulong _ping; | |||||
| private Thread _sendThread, _receiveThread; | private Thread _sendThread, _receiveThread; | ||||
| public long? CurrentServerId => _serverId; | public long? CurrentServerId => _serverId; | ||||
| public long? CurrentChannelId => _channelId; | public long? CurrentChannelId => _channelId; | ||||
| public VoiceBuffer OutputBuffer => _sendBuffer; | public VoiceBuffer OutputBuffer => _sendBuffer; | ||||
| public int Ping => (int)_ping; | |||||
| public VoiceWebSocket(DiscordWSClient client) | public VoiceWebSocket(DiscordWSClient client) | ||||
| : base(client) | : base(client) | ||||
| @@ -312,29 +314,30 @@ namespace Discord.Net.WebSockets | |||||
| } | } | ||||
| else | else | ||||
| voicePacket = new byte[MaxOpusSize + 12]; | voicePacket = new byte[MaxOpusSize + 12]; | ||||
| pingPacket = new byte[8]; | pingPacket = new byte[8]; | ||||
| pingPacket[0] = 0x80; //Flags; | pingPacket[0] = 0x80; //Flags; | ||||
| pingPacket[1] = 0x78; //Payload Type | |||||
| pingPacket[3] = 0x00; //Length | |||||
| pingPacket[4] = 0x01; //Length (1*8 bytes) | |||||
| pingPacket[5] = (byte)((_ssrc >> 24) & 0xFF); | |||||
| pingPacket[6] = (byte)((_ssrc >> 16) & 0xFF); | |||||
| pingPacket[7] = (byte)((_ssrc >> 8) & 0xFF); | |||||
| pingPacket[8] = (byte)((_ssrc >> 0) & 0xFF); | |||||
| pingPacket[1] = 0xC9; //Payload Type | |||||
| pingPacket[2] = 0x00; //Length | |||||
| pingPacket[3] = 0x01; //Length (1*8 bytes) | |||||
| pingPacket[4] = (byte)((_ssrc >> 24) & 0xFF); | |||||
| pingPacket[5] = (byte)((_ssrc >> 16) & 0xFF); | |||||
| pingPacket[6] = (byte)((_ssrc >> 8) & 0xFF); | |||||
| pingPacket[7] = (byte)((_ssrc >> 0) & 0xFF); | |||||
| if (_isEncrypted) | if (_isEncrypted) | ||||
| { | { | ||||
| Buffer.BlockCopy(pingPacket, 0, nonce, 0, 8); | Buffer.BlockCopy(pingPacket, 0, nonce, 0, 8); | ||||
| int ret = Sodium.Encrypt(pingPacket, 8, encodedFrame, 0, nonce, _secretKey); | int ret = Sodium.Encrypt(pingPacket, 8, encodedFrame, 0, nonce, _secretKey); | ||||
| if (ret != 0) | if (ret != 0) | ||||
| throw new InvalidOperationException("Failed to encrypt ping packet"); | throw new InvalidOperationException("Failed to encrypt ping packet"); | ||||
| pingPacket = new byte[ret]; | |||||
| Buffer.BlockCopy(encodedFrame, 0, pingPacket, 0, ret); | |||||
| pingPacket = new byte[pingPacket.Length + 16]; | |||||
| Buffer.BlockCopy(encodedFrame, 0, pingPacket, 0, pingPacket.Length); | |||||
| Array.Clear(nonce, 0, nonce.Length); | |||||
| } | } | ||||
| int rtpPacketLength = 0; | int rtpPacketLength = 0; | ||||
| voicePacket[0] = 0x80; //Flags; | voicePacket[0] = 0x80; //Flags; | ||||
| voicePacket[1] = 0xC9; //Payload Type | |||||
| voicePacket[1] = 0x78; //Payload Type | |||||
| voicePacket[8] = (byte)((_ssrc >> 24) & 0xFF); | voicePacket[8] = (byte)((_ssrc >> 24) & 0xFF); | ||||
| voicePacket[9] = (byte)((_ssrc >> 16) & 0xFF); | voicePacket[9] = (byte)((_ssrc >> 16) & 0xFF); | ||||
| voicePacket[10] = (byte)((_ssrc >> 8) & 0xFF); | voicePacket[10] = (byte)((_ssrc >> 8) & 0xFF); | ||||
| @@ -385,7 +388,7 @@ namespace Discord.Net.WebSockets | |||||
| } | } | ||||
| if (currentTicks > nextPingTicks) | if (currentTicks > nextPingTicks) | ||||
| { | { | ||||
| _udp.Send(pingPacket, pingPacket.Length); | |||||
| //_udp.Send(pingPacket, pingPacket.Length); | |||||
| nextPingTicks = currentTicks + 5 * Stopwatch.Frequency; | nextPingTicks = currentTicks + 5 * Stopwatch.Frequency; | ||||
| } | } | ||||
| } | } | ||||
| @@ -412,6 +415,7 @@ namespace Discord.Net.WebSockets | |||||
| protected override async Task ProcessMessage(string json) | protected override async Task ProcessMessage(string json) | ||||
| { | { | ||||
| await base.ProcessMessage(json).ConfigureAwait(false); | |||||
| var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json); | var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json); | ||||
| switch (msg.Operation) | switch (msg.Operation) | ||||
| { | { | ||||
| @@ -454,7 +458,9 @@ namespace Discord.Net.WebSockets | |||||
| break; | break; | ||||
| case 3: //PONG | case 3: //PONG | ||||
| { | { | ||||
| //var payload = (msg.Payload as JToken).ToObject<VoiceKeepAliveCommand>(); | |||||
| ulong time = EpochTime.GetMilliseconds(); | |||||
| var payload = (ulong)(long)msg.Payload; | |||||
| _ping = payload - time; | |||||
| //TODO: Use this to estimate latency | //TODO: Use this to estimate latency | ||||
| } | } | ||||
| break; | break; | ||||