@@ -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 | |||
internal sealed class JoinVoiceCommand : WebSocketMessage<JoinVoiceCommand.Data> | |||
{ | |||
@@ -110,9 +92,9 @@ namespace Discord.API | |||
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> | |||
{ | |||
@@ -48,7 +48,7 @@ namespace Discord | |||
public async Task<IDiscordVoiceClient> JoinVoiceServer(Channel 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); | |||
await client.JoinChannel(channel.Id).ConfigureAwait(false); | |||
@@ -282,7 +282,7 @@ namespace Discord | |||
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."); | |||
} | |||
protected void RaiseEvent(string name, Action action) | |||
@@ -36,12 +36,14 @@ namespace Discord.Net.WebSockets | |||
private ushort _sequence; | |||
private long? _serverId, _channelId, _userId; | |||
private string _sessionId, _token, _encryptionMode; | |||
private ulong _ping; | |||
private Thread _sendThread, _receiveThread; | |||
public long? CurrentServerId => _serverId; | |||
public long? CurrentChannelId => _channelId; | |||
public VoiceBuffer OutputBuffer => _sendBuffer; | |||
public int Ping => (int)_ping; | |||
public VoiceWebSocket(DiscordWSClient client) | |||
: base(client) | |||
@@ -312,29 +314,30 @@ namespace Discord.Net.WebSockets | |||
} | |||
else | |||
voicePacket = new byte[MaxOpusSize + 12]; | |||
pingPacket = new byte[8]; | |||
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) | |||
{ | |||
Buffer.BlockCopy(pingPacket, 0, nonce, 0, 8); | |||
int ret = Sodium.Encrypt(pingPacket, 8, encodedFrame, 0, nonce, _secretKey); | |||
if (ret != 0) | |||
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; | |||
voicePacket[0] = 0x80; //Flags; | |||
voicePacket[1] = 0xC9; //Payload Type | |||
voicePacket[1] = 0x78; //Payload Type | |||
voicePacket[8] = (byte)((_ssrc >> 24) & 0xFF); | |||
voicePacket[9] = (byte)((_ssrc >> 16) & 0xFF); | |||
voicePacket[10] = (byte)((_ssrc >> 8) & 0xFF); | |||
@@ -385,7 +388,7 @@ namespace Discord.Net.WebSockets | |||
} | |||
if (currentTicks > nextPingTicks) | |||
{ | |||
_udp.Send(pingPacket, pingPacket.Length); | |||
//_udp.Send(pingPacket, pingPacket.Length); | |||
nextPingTicks = currentTicks + 5 * Stopwatch.Frequency; | |||
} | |||
} | |||
@@ -412,6 +415,7 @@ namespace Discord.Net.WebSockets | |||
protected override async Task ProcessMessage(string json) | |||
{ | |||
await base.ProcessMessage(json).ConfigureAwait(false); | |||
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json); | |||
switch (msg.Operation) | |||
{ | |||
@@ -454,7 +458,9 @@ namespace Discord.Net.WebSockets | |||
break; | |||
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 | |||
} | |||
break; | |||