@@ -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; | ||||