Refactoring and fixed a few stylecop errors
This commit is contained in:
@@ -67,18 +67,21 @@
|
|||||||
<Compile Include="..\Discord.Net.Audio\Net\WebSockets\VoiceWebSocket.Events.cs">
|
<Compile Include="..\Discord.Net.Audio\Net\WebSockets\VoiceWebSocket.Events.cs">
|
||||||
<Link>Net\WebSockets\VoiceWebSocket.Events.cs</Link>
|
<Link>Net\WebSockets\VoiceWebSocket.Events.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\Discord.Net.Audio\Opus.cs">
|
<Compile Include="..\Discord.Net.Audio\Opus\Enums.cs">
|
||||||
<Link>Opus.cs</Link>
|
<Link>Opus\Enums.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\Discord.Net.Audio\OpusDecoder.cs">
|
<Compile Include="..\Discord.Net.Audio\Opus\OpusDecoder.cs">
|
||||||
<Link>OpusDecoder.cs</Link>
|
<Link>Opus\OpusDecoder.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\Discord.Net.Audio\OpusEncoder.cs">
|
<Compile Include="..\Discord.Net.Audio\Opus\OpusEncoder.cs">
|
||||||
<Link>OpusEncoder.cs</Link>
|
<Link>Opus\OpusEncoder.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\Discord.Net.Audio\Sodium.cs">
|
<Compile Include="..\Discord.Net.Audio\Sodium.cs">
|
||||||
<Link>Sodium.cs</Link>
|
<Link>Sodium.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\Discord.Net.Audio\Sodium\SecretBox.cs">
|
||||||
|
<Link>Sodium\SecretBox.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\Discord.Net.Audio\VoiceBuffer.cs">
|
<Compile Include="..\Discord.Net.Audio\VoiceBuffer.cs">
|
||||||
<Link>VoiceBuffer.cs</Link>
|
<Link>VoiceBuffer.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|||||||
@@ -145,27 +145,27 @@ namespace Discord.Audio
|
|||||||
return Task.FromResult(_defaultClient);
|
return Task.FromResult(_defaultClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
var client = _voiceClients.GetOrAdd(server.Id, _ =>
|
var client = _voiceClients.GetOrAdd(server.Id, (Func<long, DiscordAudioClient>)(_ =>
|
||||||
{
|
{
|
||||||
int id = unchecked(++_nextClientId);
|
int id = unchecked(++_nextClientId);
|
||||||
var logger = Client.Log().CreateLogger($"Voice #{id}");
|
var logger = Client.Log().CreateLogger($"Voice #{id}");
|
||||||
GatewayWebSocket gatewaySocket = null;
|
Net.WebSockets.GatewaySocket gatewaySocket = null;
|
||||||
var voiceSocket = new VoiceWebSocket(Client.Config, _config, logger);
|
var voiceSocket = new VoiceWebSocket(Client.Config, _config, logger);
|
||||||
var voiceClient = new DiscordAudioClient(this, id, logger, gatewaySocket, voiceSocket);
|
var voiceClient = new DiscordAudioClient((AudioService)(this), (int)id, (Logger)logger, (Net.WebSockets.GatewaySocket)gatewaySocket, (VoiceWebSocket)voiceSocket);
|
||||||
voiceClient.SetServerId(server.Id);
|
voiceClient.SetServerId(server.Id);
|
||||||
|
|
||||||
voiceSocket.OnPacket += (s, e) =>
|
voiceSocket.OnPacket += (s, e) =>
|
||||||
{
|
{
|
||||||
RaiseOnPacket(e);
|
RaiseOnPacket(e);
|
||||||
};
|
};
|
||||||
voiceSocket.IsSpeaking += (s, e) =>
|
voiceSocket.IsSpeaking += (s, e) =>
|
||||||
{
|
{
|
||||||
var user = Client.GetUser(server, e.UserId);
|
var user = Client.GetUser(server, e.UserId);
|
||||||
RaiseUserIsSpeakingUpdated(user, e.IsSpeaking);
|
RaiseUserIsSpeakingUpdated(user, e.IsSpeaking);
|
||||||
};
|
};
|
||||||
|
|
||||||
return voiceClient;
|
return voiceClient;
|
||||||
});
|
}));
|
||||||
//await client.Connect(gatewaySocket.Host, _client.Token).ConfigureAwait(false);
|
//await client.Connect(gatewaySocket.Host, _client.Token).ConfigureAwait(false);
|
||||||
return Task.FromResult(client);
|
return Task.FromResult(client);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,12 @@ namespace Discord.Audio
|
|||||||
public int? Bitrate { get { return _bitrate; } set { SetValue(ref _bitrate, value); } }
|
public int? Bitrate { get { return _bitrate; } set { SetValue(ref _bitrate, value); } }
|
||||||
private int? _bitrate = null;
|
private int? _bitrate = null;
|
||||||
|
|
||||||
//Lock
|
/// <summary> Gets or sets the number of channels (1 or 2) used for outgoing audio. </summary>
|
||||||
protected bool _isLocked;
|
public int Channels { get { return _channels; } set { SetValue(ref _channels, value); } }
|
||||||
|
private int _channels = 1;
|
||||||
|
|
||||||
|
//Lock
|
||||||
|
protected bool _isLocked;
|
||||||
internal void Lock() { _isLocked = true; }
|
internal void Lock() { _isLocked = true; }
|
||||||
protected void SetValue<T>(ref T storage, T value)
|
protected void SetValue<T>(ref T storage, T value)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ namespace Discord.Audio
|
|||||||
public int Id => _id;
|
public int Id => _id;
|
||||||
|
|
||||||
private readonly AudioService _service;
|
private readonly AudioService _service;
|
||||||
private readonly GatewayWebSocket _gatewaySocket;
|
private readonly GatewaySocket _gatewaySocket;
|
||||||
private readonly VoiceWebSocket _voiceSocket;
|
private readonly VoiceWebSocket _voiceSocket;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public long? ServerId => _voiceSocket.ServerId;
|
public long? ServerId => _voiceSocket.ServerId;
|
||||||
public long? ChannelId => _voiceSocket.ChannelId;
|
public long? ChannelId => _voiceSocket.ChannelId;
|
||||||
|
|
||||||
public DiscordAudioClient(AudioService service, int id, Logger logger, GatewayWebSocket gatewaySocket, VoiceWebSocket voiceSocket)
|
public DiscordAudioClient(AudioService service, int id, Logger logger, GatewaySocket gatewaySocket, VoiceWebSocket voiceSocket)
|
||||||
{
|
{
|
||||||
_service = service;
|
_service = service;
|
||||||
_id = id;
|
_id = id;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using Discord.API;
|
using Discord.API;
|
||||||
using Discord.Audio;
|
using Discord.Audio;
|
||||||
|
using Discord.Audio.Opus;
|
||||||
|
using Discord.Audio.Sodium;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
@@ -54,7 +56,7 @@ namespace Discord.Net.WebSockets
|
|||||||
_targetAudioBufferLength = _audioConfig.BufferLength / 20; //20 ms frames
|
_targetAudioBufferLength = _audioConfig.BufferLength / 20; //20 ms frames
|
||||||
_encodingBuffer = new byte[MaxOpusSize];
|
_encodingBuffer = new byte[MaxOpusSize];
|
||||||
_ssrcMapping = new ConcurrentDictionary<uint, long>();
|
_ssrcMapping = new ConcurrentDictionary<uint, long>();
|
||||||
_encoder = new OpusEncoder(48000, 1, 20, _audioConfig.Bitrate, Opus.Application.Audio);
|
_encoder = new OpusEncoder(48000, _audioConfig.Channels, 20, _audioConfig.Bitrate, OpusApplication.Audio);
|
||||||
_sendBuffer = new VoiceBuffer((int)Math.Ceiling(_audioConfig.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize);
|
_sendBuffer = new VoiceBuffer((int)Math.Ceiling(_audioConfig.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +225,7 @@ namespace Discord.Net.WebSockets
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
Buffer.BlockCopy(packet, 0, nonce, 0, 12);
|
Buffer.BlockCopy(packet, 0, nonce, 0, 12);
|
||||||
int ret = Sodium.Decrypt(packet, 12, packetLength - 12, decodingBuffer, nonce, _secretKey);
|
int ret = SecretBox.Decrypt(packet, 12, packetLength - 12, decodingBuffer, nonce, _secretKey);
|
||||||
if (ret != 0)
|
if (ret != 0)
|
||||||
continue;
|
continue;
|
||||||
result = decodingBuffer;
|
result = decodingBuffer;
|
||||||
@@ -294,7 +296,7 @@ namespace Discord.Net.WebSockets
|
|||||||
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 = SecretBox.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[pingPacket.Length + 16];
|
pingPacket = new byte[pingPacket.Length + 16];
|
||||||
@@ -333,7 +335,7 @@ namespace Discord.Net.WebSockets
|
|||||||
if (_isEncrypted)
|
if (_isEncrypted)
|
||||||
{
|
{
|
||||||
Buffer.BlockCopy(voicePacket, 2, nonce, 2, 6); //Update nonce
|
Buffer.BlockCopy(voicePacket, 2, nonce, 2, 6); //Update nonce
|
||||||
int ret = Sodium.Encrypt(encodedFrame, encodedLength, voicePacket, 12, nonce, _secretKey);
|
int ret = SecretBox.Encrypt(encodedFrame, encodedLength, voicePacket, 12, nonce, _secretKey);
|
||||||
if (ret != 0)
|
if (ret != 0)
|
||||||
continue;
|
continue;
|
||||||
rtpPacketLength = encodedLength + 12 + 16;
|
rtpPacketLength = encodedLength + 12 + 16;
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Discord.Audio
|
|
||||||
{
|
|
||||||
internal unsafe static class Opus
|
|
||||||
{
|
|
||||||
[DllImport("opus", EntryPoint = "opus_encoder_create", CallingConvention = CallingConvention.Cdecl)]
|
|
||||||
public static extern IntPtr CreateEncoder(int Fs, int channels, int application, out Error error);
|
|
||||||
[DllImport("opus", EntryPoint = "opus_encoder_destroy", CallingConvention = CallingConvention.Cdecl)]
|
|
||||||
public static extern void DestroyEncoder(IntPtr encoder);
|
|
||||||
[DllImport("opus", EntryPoint = "opus_encode", CallingConvention = CallingConvention.Cdecl)]
|
|
||||||
public static extern int Encode(IntPtr st, byte* pcm, int frame_size, byte[] data, int max_data_bytes);
|
|
||||||
|
|
||||||
[DllImport("opus", EntryPoint = "opus_decoder_create", CallingConvention = CallingConvention.Cdecl)]
|
|
||||||
public static extern IntPtr CreateDecoder(int Fs, int channels, out Error error);
|
|
||||||
[DllImport("opus", EntryPoint = "opus_decoder_destroy", CallingConvention = CallingConvention.Cdecl)]
|
|
||||||
public static extern void DestroyDecoder(IntPtr decoder);
|
|
||||||
[DllImport("opus", EntryPoint = "opus_decode", CallingConvention = CallingConvention.Cdecl)]
|
|
||||||
public static extern int Decode(IntPtr st, byte* data, int len, byte[] pcm, int frame_size, int decode_fec);
|
|
||||||
|
|
||||||
[DllImport("opus", EntryPoint = "opus_encoder_ctl", CallingConvention = CallingConvention.Cdecl)]
|
|
||||||
public static extern int EncoderCtl(IntPtr st, Ctl request, int value);
|
|
||||||
|
|
||||||
public enum Ctl : int
|
|
||||||
{
|
|
||||||
SetBitrateRequest = 4002,
|
|
||||||
GetBitrateRequest = 4003,
|
|
||||||
SetInbandFECRequest = 4012,
|
|
||||||
GetInbandFECRequest = 4013
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Supported coding modes.</summary>
|
|
||||||
public enum Application : int
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gives best quality at a given bitrate for voice signals. It enhances the input signal by high-pass filtering and emphasizing formants and harmonics.
|
|
||||||
/// Optionally it includes in-band forward error correction to protect against packet loss. Use this mode for typical VoIP applications.
|
|
||||||
/// Because of the enhancement, even at high bitrates the output may sound different from the input.
|
|
||||||
/// </summary>
|
|
||||||
Voip = 2048,
|
|
||||||
/// <summary>
|
|
||||||
/// Gives best quality at a given bitrate for most non-voice signals like music.
|
|
||||||
/// Use this mode for music and mixed (music/voice) content, broadcast, and applications requiring less than 15 ms of coding delay.
|
|
||||||
/// </summary>
|
|
||||||
Audio = 2049,
|
|
||||||
/// <summary> Low-delay mode that disables the speech-optimized mode in exchange for slightly reduced delay. </summary>
|
|
||||||
Restricted_LowLatency = 2051
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Error : int
|
|
||||||
{
|
|
||||||
/// <summary> No error. </summary>
|
|
||||||
OK = 0,
|
|
||||||
/// <summary> One or more invalid/out of range arguments. </summary>
|
|
||||||
BadArg = -1,
|
|
||||||
/// <summary> The mode struct passed is invalid. </summary>
|
|
||||||
BufferToSmall = -2,
|
|
||||||
/// <summary> An internal error was detected. </summary>
|
|
||||||
InternalError = -3,
|
|
||||||
/// <summary> The compressed data passed is corrupted. </summary>
|
|
||||||
InvalidPacket = -4,
|
|
||||||
/// <summary> Invalid/unsupported request number. </summary>
|
|
||||||
Unimplemented = -5,
|
|
||||||
/// <summary> An encoder or decoder structure is invalid or already freed. </summary>
|
|
||||||
InvalidState = -6,
|
|
||||||
/// <summary> Memory allocation has failed. </summary>
|
|
||||||
AllocFail = -7
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
53
src/Discord.Net.Audio/Opus/Enums.cs
Normal file
53
src/Discord.Net.Audio/Opus/Enums.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security;
|
||||||
|
|
||||||
|
namespace Discord.Audio.Opus
|
||||||
|
{
|
||||||
|
internal enum OpusCtl : int
|
||||||
|
{
|
||||||
|
SetBitrateRequest = 4002,
|
||||||
|
GetBitrateRequest = 4003,
|
||||||
|
SetInbandFECRequest = 4012,
|
||||||
|
GetInbandFECRequest = 4013
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Supported coding modes.</summary>
|
||||||
|
internal enum OpusApplication : int
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gives best quality at a given bitrate for voice signals. It enhances the input signal by high-pass filtering and emphasizing formants and harmonics.
|
||||||
|
/// Optionally it includes in-band forward error correction to protect against packet loss. Use this mode for typical VoIP applications.
|
||||||
|
/// Because of the enhancement, even at high bitrates the output may sound different from the input.
|
||||||
|
/// </summary>
|
||||||
|
Voip = 2048,
|
||||||
|
/// <summary>
|
||||||
|
/// Gives best quality at a given bitrate for most non-voice signals like music.
|
||||||
|
/// Use this mode for music and mixed (music/voice) content, broadcast, and applications requiring less than 15 ms of coding delay.
|
||||||
|
/// </summary>
|
||||||
|
Audio = 2049,
|
||||||
|
/// <summary> Low-delay mode that disables the speech-optimized mode in exchange for slightly reduced delay. </summary>
|
||||||
|
Restricted_LowLatency = 2051
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum OpusError : int
|
||||||
|
{
|
||||||
|
/// <summary> No error. </summary>
|
||||||
|
OK = 0,
|
||||||
|
/// <summary> One or more invalid/out of range arguments. </summary>
|
||||||
|
BadArg = -1,
|
||||||
|
/// <summary> The mode struct passed is invalid. </summary>
|
||||||
|
BufferToSmall = -2,
|
||||||
|
/// <summary> An internal error was detected. </summary>
|
||||||
|
InternalError = -3,
|
||||||
|
/// <summary> The compressed data passed is corrupted. </summary>
|
||||||
|
InvalidPacket = -4,
|
||||||
|
/// <summary> Invalid/unsupported request number. </summary>
|
||||||
|
Unimplemented = -5,
|
||||||
|
/// <summary> An encoder or decoder structure is invalid or already freed. </summary>
|
||||||
|
InvalidState = -6,
|
||||||
|
/// <summary> Memory allocation has failed. </summary>
|
||||||
|
AllocFail = -7
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,11 +1,26 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security;
|
||||||
|
|
||||||
namespace Discord.Audio
|
namespace Discord.Audio.Opus
|
||||||
{
|
{
|
||||||
/// <summary> Opus codec wrapper. </summary>
|
/// <summary> Opus codec wrapper. </summary>
|
||||||
internal class OpusDecoder : IDisposable
|
internal class OpusDecoder : IDisposable
|
||||||
{
|
{
|
||||||
private readonly IntPtr _ptr;
|
#if NET45
|
||||||
|
[SuppressUnmanagedCodeSecurity]
|
||||||
|
#endif
|
||||||
|
private unsafe static class UnsafeNativeMethods
|
||||||
|
{
|
||||||
|
[DllImport("opus", EntryPoint = "opus_decoder_create", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern IntPtr CreateDecoder(int Fs, int channels, out OpusError error);
|
||||||
|
[DllImport("opus", EntryPoint = "opus_decoder_destroy", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern void DestroyDecoder(IntPtr decoder);
|
||||||
|
[DllImport("opus", EntryPoint = "opus_decode", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern int Decode(IntPtr st, byte* data, int len, byte[] pcm, int frame_size, int decode_fec);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly IntPtr _ptr;
|
||||||
|
|
||||||
/// <summary> Gets the bit rate of the encoder. </summary>
|
/// <summary> Gets the bit rate of the encoder. </summary>
|
||||||
public const int BitRate = 16;
|
public const int BitRate = 16;
|
||||||
@@ -22,7 +37,7 @@ namespace Discord.Audio
|
|||||||
/// <summary> Gets the bytes per frame. </summary>
|
/// <summary> Gets the bytes per frame. </summary>
|
||||||
public int FrameSize { get; private set; }
|
public int FrameSize { get; private set; }
|
||||||
|
|
||||||
/// <summary> Creates a new Opus encoder. </summary>
|
/// <summary> Creates a new Opus decoder. </summary>
|
||||||
/// <param name="samplingRate">Sampling rate of the input signal (Hz). Supported Values: 8000, 12000, 16000, 24000, or 48000.</param>
|
/// <param name="samplingRate">Sampling rate of the input signal (Hz). Supported Values: 8000, 12000, 16000, 24000, or 48000.</param>
|
||||||
/// <param name="channels">Number of channels (1 or 2) in input signal.</param>
|
/// <param name="channels">Number of channels (1 or 2) in input signal.</param>
|
||||||
/// <param name="frameLength">Length, in milliseconds, that each frame takes. Supported Values: 2.5, 5, 10, 20, 40, 60</param>
|
/// <param name="frameLength">Length, in milliseconds, that each frame takes. Supported Values: 2.5, 5, 10, 20, 40, 60</param>
|
||||||
@@ -44,45 +59,32 @@ namespace Discord.Audio
|
|||||||
SamplesPerFrame = samplingRate / 1000 * FrameLength;
|
SamplesPerFrame = samplingRate / 1000 * FrameLength;
|
||||||
FrameSize = SamplesPerFrame * SampleSize;
|
FrameSize = SamplesPerFrame * SampleSize;
|
||||||
|
|
||||||
Opus.Error error;
|
OpusError error;
|
||||||
_ptr = Opus.CreateDecoder(samplingRate, channels, out error);
|
_ptr = UnsafeNativeMethods.CreateDecoder(samplingRate, channels, out error);
|
||||||
if (error != Opus.Error.OK)
|
if (error != OpusError.OK)
|
||||||
throw new InvalidOperationException($"Error occured while creating decoder: {error}");
|
throw new InvalidOperationException($"Error occured while creating decoder: {error}");
|
||||||
|
|
||||||
SetForwardErrorCorrection(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Produces Opus encoded audio from PCM samples. </summary>
|
/// <summary> Produces PCM samples from Opus-encoded audio. </summary>
|
||||||
/// <param name="input">PCM samples to encode.</param>
|
/// <param name="input">PCM samples to decode.</param>
|
||||||
/// <param name="inputOffset">Offset of the frame in pcmSamples.</param>
|
/// <param name="inputOffset">Offset of the frame in input.</param>
|
||||||
/// <param name="output">Buffer to store the encoded frame.</param>
|
/// <param name="output">Buffer to store the decoded frame.</param>
|
||||||
/// <returns>Length of the frame contained in outputBuffer.</returns>
|
/// <returns>Length of the frame contained in output.</returns>
|
||||||
public unsafe int DecodeFrame(byte[] input, int inputOffset, byte[] output)
|
public unsafe int DecodeFrame(byte[] input, int inputOffset, byte[] output)
|
||||||
{
|
{
|
||||||
if (disposed)
|
if (disposed)
|
||||||
throw new ObjectDisposedException(nameof(OpusDecoder));
|
throw new ObjectDisposedException(nameof(OpusDecoder));
|
||||||
|
|
||||||
int result = 0;
|
int result = 0;
|
||||||
fixed (byte* inPtr = input)
|
fixed (byte* inPtr = input)
|
||||||
result = Opus.Encode(_ptr, inPtr + inputOffset, SamplesPerFrame, output, output.Length);
|
result = UnsafeNativeMethods.Decode(_ptr, inPtr + inputOffset, SamplesPerFrame, output, output.Length, 0);
|
||||||
|
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
throw new Exception("Decoding failed: " + ((Opus.Error)result).ToString());
|
throw new Exception("Decoding failed: " + ((OpusError)result).ToString());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Gets or sets whether Forward Error Correction is enabled. </summary>
|
#region IDisposable
|
||||||
public void SetForwardErrorCorrection(bool value)
|
|
||||||
{
|
|
||||||
if (disposed)
|
|
||||||
throw new ObjectDisposedException(nameof(OpusDecoder));
|
|
||||||
|
|
||||||
var result = Opus.EncoderCtl(_ptr, Opus.Ctl.SetInbandFECRequest, value ? 1 : 0);
|
|
||||||
if (result < 0)
|
|
||||||
throw new Exception("Decoder error: " + ((Opus.Error)result).ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
#region IDisposable
|
|
||||||
private bool disposed;
|
private bool disposed;
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
@@ -92,7 +94,7 @@ namespace Discord.Audio
|
|||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
|
|
||||||
if (_ptr != IntPtr.Zero)
|
if (_ptr != IntPtr.Zero)
|
||||||
Opus.DestroyEncoder(_ptr);
|
UnsafeNativeMethods.DestroyDecoder(_ptr);
|
||||||
|
|
||||||
disposed = true;
|
disposed = true;
|
||||||
}
|
}
|
||||||
@@ -100,6 +102,6 @@ namespace Discord.Audio
|
|||||||
{
|
{
|
||||||
Dispose();
|
Dispose();
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,28 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security;
|
||||||
|
|
||||||
namespace Discord.Audio
|
namespace Discord.Audio.Opus
|
||||||
{
|
{
|
||||||
/// <summary> Opus codec wrapper. </summary>
|
/// <summary> Opus codec wrapper. </summary>
|
||||||
internal class OpusEncoder : IDisposable
|
internal class OpusEncoder : IDisposable
|
||||||
{
|
{
|
||||||
private readonly IntPtr _ptr;
|
#if NET45
|
||||||
|
[SuppressUnmanagedCodeSecurity]
|
||||||
|
#endif
|
||||||
|
private unsafe static class UnsafeNativeMethods
|
||||||
|
{
|
||||||
|
[DllImport("opus", EntryPoint = "opus_encoder_create", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern IntPtr CreateEncoder(int Fs, int channels, int application, out OpusError error);
|
||||||
|
[DllImport("opus", EntryPoint = "opus_encoder_destroy", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern void DestroyEncoder(IntPtr encoder);
|
||||||
|
[DllImport("opus", EntryPoint = "opus_encode", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern int Encode(IntPtr st, byte* pcm, int frame_size, byte[] data, int max_data_bytes);
|
||||||
|
[DllImport("opus", EntryPoint = "opus_encoder_ctl", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern int EncoderCtl(IntPtr st, OpusCtl request, int value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly IntPtr _ptr;
|
||||||
|
|
||||||
/// <summary> Gets the bit rate of the encoder. </summary>
|
/// <summary> Gets the bit rate of the encoder. </summary>
|
||||||
public const int BitsPerSample = 16;
|
public const int BitsPerSample = 16;
|
||||||
@@ -24,7 +41,7 @@ namespace Discord.Audio
|
|||||||
/// <summary> Gets the bit rate in kbit/s. </summary>
|
/// <summary> Gets the bit rate in kbit/s. </summary>
|
||||||
public int? BitRate { get; private set; }
|
public int? BitRate { get; private set; }
|
||||||
/// <summary> Gets the coding mode of the encoder. </summary>
|
/// <summary> Gets the coding mode of the encoder. </summary>
|
||||||
public Opus.Application Application { get; private set; }
|
public OpusApplication Application { get; private set; }
|
||||||
|
|
||||||
/// <summary> Creates a new Opus encoder. </summary>
|
/// <summary> Creates a new Opus encoder. </summary>
|
||||||
/// <param name="samplingRate">Sampling rate of the input signal (Hz). Supported Values: 8000, 12000, 16000, 24000, or 48000.</param>
|
/// <param name="samplingRate">Sampling rate of the input signal (Hz). Supported Values: 8000, 12000, 16000, 24000, or 48000.</param>
|
||||||
@@ -33,7 +50,7 @@ namespace Discord.Audio
|
|||||||
/// <param name="bitrate">Bitrate (kbit/s) used for this encoder. Supported Values: 1-512. Null will use the recommended bitrate. </param>
|
/// <param name="bitrate">Bitrate (kbit/s) used for this encoder. Supported Values: 1-512. Null will use the recommended bitrate. </param>
|
||||||
/// <param name="application">Coding mode.</param>
|
/// <param name="application">Coding mode.</param>
|
||||||
/// <returns>A new <c>OpusEncoder</c></returns>
|
/// <returns>A new <c>OpusEncoder</c></returns>
|
||||||
public OpusEncoder(int samplingRate, int channels, int frameLength, int? bitrate, Opus.Application application)
|
public OpusEncoder(int samplingRate, int channels, int frameLength, int? bitrate, OpusApplication application)
|
||||||
{
|
{
|
||||||
if (samplingRate != 8000 && samplingRate != 12000 &&
|
if (samplingRate != 8000 && samplingRate != 12000 &&
|
||||||
samplingRate != 16000 && samplingRate != 24000 &&
|
samplingRate != 16000 && samplingRate != 24000 &&
|
||||||
@@ -53,9 +70,9 @@ namespace Discord.Audio
|
|||||||
FrameSize = SamplesPerFrame * SampleSize;
|
FrameSize = SamplesPerFrame * SampleSize;
|
||||||
BitRate = bitrate;
|
BitRate = bitrate;
|
||||||
|
|
||||||
Opus.Error error;
|
OpusError error;
|
||||||
_ptr = Opus.CreateEncoder(samplingRate, channels, (int)application, out error);
|
_ptr = UnsafeNativeMethods.CreateEncoder(samplingRate, channels, (int)application, out error);
|
||||||
if (error != Opus.Error.OK)
|
if (error != OpusError.OK)
|
||||||
throw new InvalidOperationException($"Error occured while creating encoder: {error}");
|
throw new InvalidOperationException($"Error occured while creating encoder: {error}");
|
||||||
|
|
||||||
SetForwardErrorCorrection(true);
|
SetForwardErrorCorrection(true);
|
||||||
@@ -75,10 +92,10 @@ namespace Discord.Audio
|
|||||||
|
|
||||||
int result = 0;
|
int result = 0;
|
||||||
fixed (byte* inPtr = input)
|
fixed (byte* inPtr = input)
|
||||||
result = Opus.Encode(_ptr, inPtr + inputOffset, SamplesPerFrame, output, output.Length);
|
result = UnsafeNativeMethods.Encode(_ptr, inPtr + inputOffset, SamplesPerFrame, output, output.Length);
|
||||||
|
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
throw new Exception("Encoding failed: " + ((Opus.Error)result).ToString());
|
throw new Exception("Encoding failed: " + ((OpusError)result).ToString());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,9 +105,9 @@ namespace Discord.Audio
|
|||||||
if (disposed)
|
if (disposed)
|
||||||
throw new ObjectDisposedException(nameof(OpusEncoder));
|
throw new ObjectDisposedException(nameof(OpusEncoder));
|
||||||
|
|
||||||
var result = Opus.EncoderCtl(_ptr, Opus.Ctl.SetInbandFECRequest, value ? 1 : 0);
|
var result = UnsafeNativeMethods.EncoderCtl(_ptr, OpusCtl.SetInbandFECRequest, value ? 1 : 0);
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
throw new Exception("Encoder error: " + ((Opus.Error)result).ToString());
|
throw new Exception("Encoder error: " + ((OpusError)result).ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Gets or sets whether Forward Error Correction is enabled. </summary>
|
/// <summary> Gets or sets whether Forward Error Correction is enabled. </summary>
|
||||||
@@ -99,9 +116,9 @@ namespace Discord.Audio
|
|||||||
if (disposed)
|
if (disposed)
|
||||||
throw new ObjectDisposedException(nameof(OpusEncoder));
|
throw new ObjectDisposedException(nameof(OpusEncoder));
|
||||||
|
|
||||||
var result = Opus.EncoderCtl(_ptr, Opus.Ctl.SetBitrateRequest, value * 1000);
|
var result = UnsafeNativeMethods.EncoderCtl(_ptr, OpusCtl.SetBitrateRequest, value * 1000);
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
throw new Exception("Encoder error: " + ((Opus.Error)result).ToString());
|
throw new Exception("Encoder error: " + ((OpusError)result).ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
#region IDisposable
|
#region IDisposable
|
||||||
@@ -114,7 +131,7 @@ namespace Discord.Audio
|
|||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
|
|
||||||
if (_ptr != IntPtr.Zero)
|
if (_ptr != IntPtr.Zero)
|
||||||
Opus.DestroyEncoder(_ptr);
|
UnsafeNativeMethods.DestroyEncoder(_ptr);
|
||||||
|
|
||||||
disposed = true;
|
disposed = true;
|
||||||
}
|
}
|
||||||
@@ -2,25 +2,4 @@
|
|||||||
|
|
||||||
namespace Discord.Audio
|
namespace Discord.Audio
|
||||||
{
|
{
|
||||||
internal unsafe static class Sodium
|
|
||||||
{
|
|
||||||
[DllImport("libsodium", EntryPoint = "crypto_secretbox_easy", CallingConvention = CallingConvention.Cdecl)]
|
|
||||||
private static extern int SecretBoxEasy(byte* output, byte[] input, long inputLength, byte[] nonce, byte[] secret);
|
|
||||||
|
|
||||||
public static int Encrypt(byte[] input, long inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret)
|
|
||||||
{
|
|
||||||
fixed (byte* outPtr = output)
|
|
||||||
return SecretBoxEasy(outPtr + outputOffset, input, inputLength, nonce, secret);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[DllImport("libsodium", EntryPoint = "crypto_secretbox_open_easy", CallingConvention = CallingConvention.Cdecl)]
|
|
||||||
private static extern int SecretBoxOpenEasy(byte[] output, byte* input, long inputLength, byte[] nonce, byte[] secret);
|
|
||||||
|
|
||||||
public static int Decrypt(byte[] input, int inputOffset, long inputLength, byte[] output, byte[] nonce, byte[] secret)
|
|
||||||
{
|
|
||||||
fixed (byte* inPtr = input)
|
|
||||||
return SecretBoxOpenEasy(output, inPtr + inputLength, inputLength, nonce, secret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
30
src/Discord.Net.Audio/Sodium/SecretBox.cs
Normal file
30
src/Discord.Net.Audio/Sodium/SecretBox.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security;
|
||||||
|
|
||||||
|
namespace Discord.Audio.Sodium
|
||||||
|
{
|
||||||
|
public unsafe static class SecretBox
|
||||||
|
{
|
||||||
|
#if NET45
|
||||||
|
[SuppressUnmanagedCodeSecurity]
|
||||||
|
#endif
|
||||||
|
private static class SafeNativeMethods
|
||||||
|
{
|
||||||
|
[DllImport("libsodium", EntryPoint = "crypto_secretbox_easy", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern int SecretBoxEasy(byte* output, byte[] input, long inputLength, byte[] nonce, byte[] secret);
|
||||||
|
[DllImport("libsodium", EntryPoint = "crypto_secretbox_open_easy", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern int SecretBoxOpenEasy(byte[] output, byte* input, long inputLength, byte[] nonce, byte[] secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int Encrypt(byte[] input, long inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret)
|
||||||
|
{
|
||||||
|
fixed (byte* outPtr = output)
|
||||||
|
return SafeNativeMethods.SecretBoxEasy(outPtr + outputOffset, input, inputLength, nonce, secret);
|
||||||
|
}
|
||||||
|
public static int Decrypt(byte[] input, int inputOffset, long inputLength, byte[] output, byte[] nonce, byte[] secret)
|
||||||
|
{
|
||||||
|
fixed (byte* inPtr = input)
|
||||||
|
return SafeNativeMethods.SecretBoxOpenEasy(output, inPtr + inputLength, inputLength, nonce, secret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Discord.Commands
|
namespace Discord.Commands
|
||||||
{
|
{
|
||||||
public class CommandEventArgs
|
public class CommandEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
private readonly string[] _args;
|
private readonly string[] _args;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
|
|||||||
namespace Discord.Commands
|
namespace Discord.Commands
|
||||||
{
|
{
|
||||||
/// <summary> A Discord.Net client with extensions for handling common bot operations like text commands. </summary>
|
/// <summary> A Discord.Net client with extensions for handling common bot operations like text commands. </summary>
|
||||||
public partial class CommandService : IService
|
public sealed partial class CommandService : IService
|
||||||
{
|
{
|
||||||
private const string DefaultPermissionError = "You do not have permission to access this command.";
|
private const string DefaultPermissionError = "You do not have permission to access this command.";
|
||||||
|
|
||||||
|
|||||||
@@ -224,11 +224,11 @@
|
|||||||
<Compile Include="..\Discord.Net\Net\TimeoutException.cs">
|
<Compile Include="..\Discord.Net\Net\TimeoutException.cs">
|
||||||
<Link>Net\TimeoutException.cs</Link>
|
<Link>Net\TimeoutException.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\Discord.Net\Net\WebSockets\GatewayWebSocket.cs">
|
<Compile Include="..\Discord.Net\Net\WebSockets\GatewaySocket.cs">
|
||||||
<Link>Net\WebSockets\GatewayWebSocket.cs</Link>
|
<Link>Net\WebSockets\GatewaySocket.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\Discord.Net\Net\WebSockets\GatewayWebSockets.Events.cs">
|
<Compile Include="..\Discord.Net\Net\WebSockets\GatewaySocket.Events.cs">
|
||||||
<Link>Net\WebSockets\GatewayWebSockets.Events.cs</Link>
|
<Link>Net\WebSockets\GatewaySocket.Events.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\Discord.Net\Net\WebSockets\IWebSocketEngine.cs">
|
<Compile Include="..\Discord.Net\Net\WebSockets\IWebSocketEngine.cs">
|
||||||
<Link>Net\WebSockets\IWebSocketEngine.cs</Link>
|
<Link>Net\WebSockets\IWebSocketEngine.cs</Link>
|
||||||
|
|||||||
@@ -9,165 +9,165 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace Discord.API
|
namespace Discord.API
|
||||||
{
|
{
|
||||||
public enum GatewayOpCodes : byte
|
public enum GatewayOpCodes : byte
|
||||||
{
|
{
|
||||||
/// <summary> Client <-- Server - Used to send most events. </summary>
|
/// <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>
|
/// <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>
|
/// <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>
|
/// <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>
|
/// <summary> Client --> Server - Used to join a particular voice channel. </summary>
|
||||||
VoiceStateUpdate = 4,
|
VoiceStateUpdate = 4,
|
||||||
/// <summary> Client --> Server - Used to ensure the server's voice server is alive. Only send this if voice connection fails or suddenly drops. </summary>
|
/// <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,
|
VoiceServerPing = 5,
|
||||||
/// <summary> Client --> Server - Used to resume a connection after a redirect occurs. </summary>
|
/// <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>
|
/// <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>
|
/// <summary> Client --> Server - Used to request all members that were withheld by large_threshold </summary>
|
||||||
RequestGuildMembers = 8
|
RequestGuildMembers = 8
|
||||||
}
|
}
|
||||||
|
|
||||||
//Common
|
//Common
|
||||||
public class WebSocketMessage
|
public class WebSocketMessage
|
||||||
{
|
{
|
||||||
public WebSocketMessage() { }
|
public WebSocketMessage() { }
|
||||||
public WebSocketMessage(int op) { Operation = op; }
|
public WebSocketMessage(int op) { Operation = op; }
|
||||||
|
|
||||||
[JsonProperty("op")]
|
[JsonProperty("op")]
|
||||||
public int Operation;
|
public int Operation;
|
||||||
[JsonProperty("d")]
|
[JsonProperty("d")]
|
||||||
public object Payload;
|
public object Payload;
|
||||||
[JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public string Type;
|
public string Type;
|
||||||
[JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public int? Sequence;
|
public int? Sequence;
|
||||||
}
|
}
|
||||||
public abstract class WebSocketMessage<T> : WebSocketMessage
|
public abstract class WebSocketMessage<T> : WebSocketMessage
|
||||||
where T : new()
|
where T : new()
|
||||||
{
|
{
|
||||||
public WebSocketMessage() { Payload = new T(); }
|
public WebSocketMessage() { Payload = new T(); }
|
||||||
public WebSocketMessage(int op) : base(op) { Payload = new T(); }
|
public WebSocketMessage(int op) : base(op) { Payload = new T(); }
|
||||||
public WebSocketMessage(int op, T payload) : base(op) { Payload = payload; }
|
public WebSocketMessage(int op, T payload) : base(op) { Payload = payload; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public new T Payload
|
public new T Payload
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (base.Payload is JToken)
|
if (base.Payload is JToken)
|
||||||
base.Payload = (base.Payload as JToken).ToObject<T>();
|
base.Payload = (base.Payload as JToken).ToObject<T>();
|
||||||
return (T)base.Payload;
|
return (T)base.Payload;
|
||||||
}
|
}
|
||||||
set { base.Payload = value; }
|
set { base.Payload = value; }
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Commands
|
|
||||||
internal sealed class HeartbeatCommand : WebSocketMessage<long>
|
|
||||||
{
|
|
||||||
public HeartbeatCommand() : base((int)GatewayOpCodes.Heartbeat, EpochTime.GetMilliseconds()) { }
|
|
||||||
}
|
|
||||||
internal sealed class IdentifyCommand : WebSocketMessage<IdentifyCommand.Data>
|
|
||||||
{
|
|
||||||
public IdentifyCommand() : base((int)GatewayOpCodes.Identify) { }
|
|
||||||
public class Data
|
|
||||||
{
|
|
||||||
[JsonProperty("token")]
|
|
||||||
public string Token;
|
|
||||||
[JsonProperty("v")]
|
|
||||||
public int Version = 3;
|
|
||||||
[JsonProperty("properties")]
|
|
||||||
public Dictionary<string, string> Properties = new Dictionary<string, string>();
|
|
||||||
[JsonProperty("large_threshold", NullValueHandling = NullValueHandling.Ignore)]
|
|
||||||
public int? LargeThreshold;
|
|
||||||
[JsonProperty("compress", NullValueHandling = NullValueHandling.Ignore)]
|
|
||||||
public bool? Compress;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class StatusUpdateCommand : WebSocketMessage<StatusUpdateCommand.Data>
|
//Commands
|
||||||
{
|
internal sealed class HeartbeatCommand : WebSocketMessage<long>
|
||||||
public StatusUpdateCommand() : base((int)GatewayOpCodes.StatusUpdate) { }
|
{
|
||||||
public class Data
|
public HeartbeatCommand() : base((int)GatewayOpCodes.Heartbeat, EpochTime.GetMilliseconds()) { }
|
||||||
{
|
}
|
||||||
[JsonProperty("idle_since")]
|
internal sealed class IdentifyCommand : WebSocketMessage<IdentifyCommand.Data>
|
||||||
public long? IdleSince;
|
{
|
||||||
[JsonProperty("game_id")]
|
public IdentifyCommand() : base((int)GatewayOpCodes.Identify) { }
|
||||||
public int? GameId;
|
public class Data
|
||||||
}
|
{
|
||||||
}
|
[JsonProperty("token")]
|
||||||
|
public string Token;
|
||||||
|
[JsonProperty("v")]
|
||||||
|
public int Version = 3;
|
||||||
|
[JsonProperty("properties")]
|
||||||
|
public Dictionary<string, string> Properties = new Dictionary<string, string>();
|
||||||
|
[JsonProperty("large_threshold", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
|
public int? LargeThreshold;
|
||||||
|
[JsonProperty("compress", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
|
public bool? Compress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal sealed class JoinVoiceCommand : WebSocketMessage<JoinVoiceCommand.Data>
|
internal sealed class StatusUpdateCommand : WebSocketMessage<StatusUpdateCommand.Data>
|
||||||
{
|
{
|
||||||
public JoinVoiceCommand() : base((int)GatewayOpCodes.VoiceStateUpdate) { }
|
public StatusUpdateCommand() : base((int)GatewayOpCodes.StatusUpdate) { }
|
||||||
public class Data
|
public class Data
|
||||||
{
|
{
|
||||||
[JsonProperty("guild_id")]
|
[JsonProperty("idle_since")]
|
||||||
[JsonConverter(typeof(LongStringConverter))]
|
public long? IdleSince;
|
||||||
public long ServerId;
|
[JsonProperty("game_id")]
|
||||||
[JsonProperty("channel_id")]
|
public int? GameId;
|
||||||
[JsonConverter(typeof(LongStringConverter))]
|
}
|
||||||
public long ChannelId;
|
}
|
||||||
[JsonProperty("self_mute")]
|
|
||||||
public string SelfMute;
|
|
||||||
[JsonProperty("self_deaf")]
|
|
||||||
public string SelfDeaf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class ResumeCommand : WebSocketMessage<ResumeCommand.Data>
|
internal sealed class JoinVoiceCommand : WebSocketMessage<JoinVoiceCommand.Data>
|
||||||
{
|
{
|
||||||
public ResumeCommand() : base((int)GatewayOpCodes.Resume) { }
|
public JoinVoiceCommand() : base((int)GatewayOpCodes.VoiceStateUpdate) { }
|
||||||
public class Data
|
public class Data
|
||||||
{
|
{
|
||||||
[JsonProperty("session_id")]
|
[JsonProperty("guild_id")]
|
||||||
public string SessionId;
|
[JsonConverter(typeof(LongStringConverter))]
|
||||||
[JsonProperty("seq")]
|
public long ServerId;
|
||||||
public int Sequence;
|
[JsonProperty("channel_id")]
|
||||||
}
|
[JsonConverter(typeof(LongStringConverter))]
|
||||||
}
|
public long ChannelId;
|
||||||
|
[JsonProperty("self_mute")]
|
||||||
|
public string SelfMute;
|
||||||
|
[JsonProperty("self_deaf")]
|
||||||
|
public string SelfDeaf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Events
|
internal sealed class ResumeCommand : WebSocketMessage<ResumeCommand.Data>
|
||||||
internal sealed class ReadyEvent
|
{
|
||||||
{
|
public ResumeCommand() : base((int)GatewayOpCodes.Resume) { }
|
||||||
public sealed class ReadStateInfo
|
public class Data
|
||||||
{
|
{
|
||||||
[JsonProperty("id")]
|
[JsonProperty("session_id")]
|
||||||
public string ChannelId;
|
public string SessionId;
|
||||||
[JsonProperty("mention_count")]
|
[JsonProperty("seq")]
|
||||||
public int MentionCount;
|
public int Sequence;
|
||||||
[JsonProperty("last_message_id")]
|
}
|
||||||
public string LastMessageId;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty("v")]
|
//Events
|
||||||
public int Version;
|
internal sealed class ReadyEvent
|
||||||
[JsonProperty("user")]
|
{
|
||||||
public UserInfo User;
|
public sealed class ReadStateInfo
|
||||||
[JsonProperty("session_id")]
|
{
|
||||||
public string SessionId;
|
[JsonProperty("id")]
|
||||||
[JsonProperty("read_state")]
|
public string ChannelId;
|
||||||
public ReadStateInfo[] ReadState;
|
[JsonProperty("mention_count")]
|
||||||
[JsonProperty("guilds")]
|
public int MentionCount;
|
||||||
public ExtendedGuildInfo[] Guilds;
|
[JsonProperty("last_message_id")]
|
||||||
[JsonProperty("private_channels")]
|
public string LastMessageId;
|
||||||
public ChannelInfo[] PrivateChannels;
|
}
|
||||||
[JsonProperty("heartbeat_interval")]
|
|
||||||
public int HeartbeatInterval;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class RedirectEvent
|
[JsonProperty("v")]
|
||||||
{
|
public int Version;
|
||||||
[JsonProperty("url")]
|
[JsonProperty("user")]
|
||||||
public string Url;
|
public UserInfo User;
|
||||||
}
|
[JsonProperty("session_id")]
|
||||||
internal sealed class ResumeEvent
|
public string SessionId;
|
||||||
{
|
[JsonProperty("read_state")]
|
||||||
[JsonProperty("heartbeat_interval")]
|
public ReadStateInfo[] ReadState;
|
||||||
public int HeartbeatInterval;
|
[JsonProperty("guilds")]
|
||||||
}
|
public ExtendedGuildInfo[] Guilds;
|
||||||
|
[JsonProperty("private_channels")]
|
||||||
|
public ChannelInfo[] PrivateChannels;
|
||||||
|
[JsonProperty("heartbeat_interval")]
|
||||||
|
public int HeartbeatInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class RedirectEvent
|
||||||
|
{
|
||||||
|
[JsonProperty("url")]
|
||||||
|
public string Url;
|
||||||
|
}
|
||||||
|
internal sealed class ResumeEvent
|
||||||
|
{
|
||||||
|
[JsonProperty("heartbeat_interval")]
|
||||||
|
public int HeartbeatInterval;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class DiscordClient
|
public partial class DiscordClient : IDisposable
|
||||||
{
|
{
|
||||||
public event EventHandler<UserEventArgs> UserJoined;
|
public event EventHandler<UserEventArgs> UserJoined;
|
||||||
private void RaiseUserJoined(User user)
|
private void RaiseUserJoined(User user)
|
||||||
@@ -305,5 +305,5 @@ namespace Discord
|
|||||||
_webSocket.SendStatusUpdate(_status == UserStatus.Idle ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (long?)null, _gameId);
|
_webSocket.SendStatusUpdate(_status == UserStatus.Idle ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (long?)null, _gameId);
|
||||||
return TaskHelper.CompletedTask;
|
return TaskHelper.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,6 @@ using Newtonsoft.Json;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.ExceptionServices;
|
using System.Runtime.ExceptionServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -82,8 +81,8 @@ namespace Discord
|
|||||||
private readonly DiscordAPIClient _api;
|
private readonly DiscordAPIClient _api;
|
||||||
|
|
||||||
/// <summary> Returns the internal websocket object. </summary>
|
/// <summary> Returns the internal websocket object. </summary>
|
||||||
public GatewayWebSocket WebSocket => _webSocket;
|
public GatewaySocket WebSocket => _webSocket;
|
||||||
private readonly GatewayWebSocket _webSocket;
|
private readonly GatewaySocket _webSocket;
|
||||||
|
|
||||||
public string GatewayUrl => _gateway;
|
public string GatewayUrl => _gateway;
|
||||||
private string _gateway;
|
private string _gateway;
|
||||||
@@ -140,11 +139,24 @@ namespace Discord
|
|||||||
CreateCacheLogger();
|
CreateCacheLogger();
|
||||||
|
|
||||||
//Networking
|
//Networking
|
||||||
_webSocket = CreateWebSocket();
|
_webSocket = new GatewaySocket(_config, _log.CreateLogger("WebSocket"));
|
||||||
_api = new DiscordAPIClient(_config);
|
var settings = new JsonSerializerSettings();
|
||||||
|
_webSocket.Connected += (s, e) =>
|
||||||
|
{
|
||||||
|
if (_state == (int)DiscordClientState.Connecting)
|
||||||
|
EndConnect();
|
||||||
|
};
|
||||||
|
_webSocket.Disconnected += (s, e) =>
|
||||||
|
{
|
||||||
|
RaiseDisconnected(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
_webSocket.ReceivedDispatch += async (s, e) => await OnReceivedEvent(e).ConfigureAwait(false);
|
||||||
|
|
||||||
|
_api = new DiscordAPIClient(_config);
|
||||||
if (Config.UseMessageQueue)
|
if (Config.UseMessageQueue)
|
||||||
_pendingMessages = new ConcurrentQueue<Message>();
|
_pendingMessages = new ConcurrentQueue<Message>();
|
||||||
this.Connected += async (s, e) =>
|
Connected += async (s, e) =>
|
||||||
{
|
{
|
||||||
_api.CancelToken = _cancelToken;
|
_api.CancelToken = _cancelToken;
|
||||||
await SendStatus().ConfigureAwait(false);
|
await SendStatus().ConfigureAwait(false);
|
||||||
@@ -257,24 +269,6 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private GatewayWebSocket CreateWebSocket()
|
|
||||||
{
|
|
||||||
var socket = new GatewayWebSocket(_config, _log.CreateLogger("WebSocket"));
|
|
||||||
var settings = new JsonSerializerSettings();
|
|
||||||
socket.Connected += (s, e) =>
|
|
||||||
{
|
|
||||||
if (_state == (int)DiscordClientState.Connecting)
|
|
||||||
CompleteConnect();
|
|
||||||
};
|
|
||||||
socket.Disconnected += (s, e) =>
|
|
||||||
{
|
|
||||||
RaiseDisconnected(e);
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.ReceivedDispatch += async (s, e) => await OnReceivedEvent(e).ConfigureAwait(false);
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Connects to the Discord server with the provided email and password. </summary>
|
/// <summary> Connects to the Discord server with the provided email and password. </summary>
|
||||||
/// <returns> Returns a token for future connections. </returns>
|
/// <returns> Returns a token for future connections. </returns>
|
||||||
public async Task<string> Connect(string email, string password)
|
public async Task<string> Connect(string email, string password)
|
||||||
@@ -285,73 +279,73 @@ namespace Discord
|
|||||||
if (State != DiscordClientState.Disconnected)
|
if (State != DiscordClientState.Disconnected)
|
||||||
await Disconnect().ConfigureAwait(false);
|
await Disconnect().ConfigureAwait(false);
|
||||||
|
|
||||||
string token;
|
var response = await _api.Login(email, password)
|
||||||
try
|
.ConfigureAwait(false);
|
||||||
{
|
_token = response.Token;
|
||||||
var response = await _api.Login(email, password)
|
_api.Token = response.Token;
|
||||||
.ConfigureAwait(false);
|
if (_config.LogLevel >= LogSeverity.Verbose)
|
||||||
token = response.Token;
|
_logger.Log(LogSeverity.Verbose, "Login successful, got token.");
|
||||||
if (_config.LogLevel >= LogSeverity.Verbose)
|
|
||||||
_logger.Log(LogSeverity.Verbose, "Login successful, got token.");
|
|
||||||
|
|
||||||
await Connect(token);
|
await BeginConnect();
|
||||||
return token;
|
return response.Token;
|
||||||
}
|
}
|
||||||
catch (TaskCanceledException) { throw new TimeoutException(); }
|
|
||||||
}
|
|
||||||
/// <summary> Connects to the Discord server with the provided token. </summary>
|
/// <summary> Connects to the Discord server with the provided token. </summary>
|
||||||
public async Task Connect(string token)
|
public async Task Connect(string token)
|
||||||
{
|
{
|
||||||
if (!_sentInitialLog)
|
if (!_sentInitialLog)
|
||||||
SendInitialLog();
|
SendInitialLog();
|
||||||
|
|
||||||
if (State != (int)DiscordClientState.Disconnected)
|
if (State != (int)DiscordClientState.Disconnected)
|
||||||
await Disconnect().ConfigureAwait(false);
|
await Disconnect().ConfigureAwait(false);
|
||||||
|
|
||||||
_api.Token = token;
|
_token = token;
|
||||||
var gatewayResponse = await _api.Gateway().ConfigureAwait(false);
|
_api.Token = token;
|
||||||
string gateway = gatewayResponse.Url;
|
await BeginConnect();
|
||||||
if (_config.LogLevel >= LogSeverity.Verbose)
|
}
|
||||||
_logger.Log(LogSeverity.Verbose, $"Websocket endpoint: {gateway}");
|
|
||||||
|
|
||||||
try
|
private async Task BeginConnect()
|
||||||
{
|
{
|
||||||
_state = (int)DiscordClientState.Connecting;
|
try
|
||||||
_disconnectedEvent.Reset();
|
{
|
||||||
|
_state = (int)DiscordClientState.Connecting;
|
||||||
|
|
||||||
_gateway = gateway;
|
var gatewayResponse = await _api.Gateway().ConfigureAwait(false);
|
||||||
_token = token;
|
string gateway = gatewayResponse.Url;
|
||||||
|
if (_config.LogLevel >= LogSeverity.Verbose)
|
||||||
|
_logger.Log(LogSeverity.Verbose, $"Websocket endpoint: {gateway}");
|
||||||
|
|
||||||
_cancelTokenSource = new CancellationTokenSource();
|
_disconnectedEvent.Reset();
|
||||||
_cancelToken = _cancelTokenSource.Token;
|
|
||||||
|
|
||||||
_webSocket.Host = gateway;
|
_gateway = gateway;
|
||||||
_webSocket.ParentCancelToken = _cancelToken;
|
|
||||||
await _webSocket.Connect(token).ConfigureAwait(false);
|
|
||||||
|
|
||||||
_runTask = RunTasks();
|
_cancelTokenSource = new CancellationTokenSource();
|
||||||
|
_cancelToken = _cancelTokenSource.Token;
|
||||||
|
|
||||||
try
|
_webSocket.Host = gateway;
|
||||||
{
|
_webSocket.ParentCancelToken = _cancelToken;
|
||||||
//Cancel if either Disconnect is called, data socket errors or timeout is reached
|
await _webSocket.Connect(_token).ConfigureAwait(false);
|
||||||
var cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, _webSocket.CancelToken).Token;
|
|
||||||
_connectedEvent.Wait(cancelToken);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
_webSocket.ThrowError(); //Throws data socket's internal error if any occured
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
//_state = (int)DiscordClientState.Connected;
|
_runTask = RunTasks();
|
||||||
}
|
|
||||||
catch
|
try
|
||||||
{
|
{
|
||||||
await Disconnect().ConfigureAwait(false);
|
//Cancel if either Disconnect is called, data socket errors or timeout is reached
|
||||||
throw;
|
var cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, _webSocket.CancelToken).Token;
|
||||||
}
|
_connectedEvent.Wait(cancelToken);
|
||||||
}
|
}
|
||||||
private void CompleteConnect()
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
_webSocket.ThrowError(); //Throws data socket's internal error if any occured
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await Disconnect().ConfigureAwait(false);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void EndConnect()
|
||||||
{
|
{
|
||||||
_state = (int)DiscordClientState.Connected;
|
_state = (int)DiscordClientState.Connected;
|
||||||
_connectedEvent.Set();
|
_connectedEvent.Set();
|
||||||
@@ -359,8 +353,8 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Disconnects from the Discord server, canceling any pending requests. </summary>
|
/// <summary> Disconnects from the Discord server, canceling any pending requests. </summary>
|
||||||
public Task Disconnect() => DisconnectInternal(new Exception("Disconnect was requested by user."), isUnexpected: false);
|
public Task Disconnect() => SignalDisconnect(new Exception("Disconnect was requested by user."), isUnexpected: false);
|
||||||
private async Task DisconnectInternal(Exception ex = null, bool isUnexpected = true, bool skipAwait = false)
|
private async Task SignalDisconnect(Exception ex = null, bool isUnexpected = true, bool wait = false)
|
||||||
{
|
{
|
||||||
int oldState;
|
int oldState;
|
||||||
bool hasWriterLock;
|
bool hasWriterLock;
|
||||||
@@ -386,7 +380,7 @@ namespace Discord
|
|||||||
await Cleanup().ConfigureAwait(false);*/
|
await Cleanup().ConfigureAwait(false);*/
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!skipAwait)
|
if (wait)
|
||||||
{
|
{
|
||||||
Task task = _runTask;
|
Task task = _runTask;
|
||||||
if (_runTask != null)
|
if (_runTask != null)
|
||||||
@@ -407,10 +401,10 @@ namespace Discord
|
|||||||
|
|
||||||
//Wait until the first task ends/errors and capture the error
|
//Wait until the first task ends/errors and capture the error
|
||||||
try { await firstTask.ConfigureAwait(false); }
|
try { await firstTask.ConfigureAwait(false); }
|
||||||
catch (Exception ex) { await DisconnectInternal(ex: ex, skipAwait: true).ConfigureAwait(false); }
|
catch (Exception ex) { await SignalDisconnect(ex: ex, wait: true).ConfigureAwait(false); }
|
||||||
|
|
||||||
//Ensure all other tasks are signaled to end.
|
//Ensure all other tasks are signaled to end.
|
||||||
await DisconnectInternal(skipAwait: true).ConfigureAwait(false);
|
await SignalDisconnect(wait: true).ConfigureAwait(false);
|
||||||
|
|
||||||
//Wait for the remaining tasks to complete
|
//Wait for the remaining tasks to complete
|
||||||
try { await allTasks.ConfigureAwait(false); }
|
try { await allTasks.ConfigureAwait(false); }
|
||||||
@@ -453,35 +447,6 @@ namespace Discord
|
|||||||
|
|
||||||
_privateUser = null;
|
_privateUser = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public T AddSingleton<T>(T obj)
|
|
||||||
where T : class
|
|
||||||
{
|
|
||||||
_singletons.Add(typeof(T), obj);
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
public T GetSingleton<T>(bool required = true)
|
|
||||||
where T : class
|
|
||||||
{
|
|
||||||
object singleton;
|
|
||||||
T singletonT = null;
|
|
||||||
if (_singletons.TryGetValue(typeof(T), out singleton))
|
|
||||||
singletonT = singleton as T;
|
|
||||||
|
|
||||||
if (singletonT == null && required)
|
|
||||||
throw new InvalidOperationException($"This operation requires {typeof(T).Name} to be added to {nameof(DiscordClient)}.");
|
|
||||||
return singletonT;
|
|
||||||
}
|
|
||||||
public T AddService<T>(T obj)
|
|
||||||
where T : class, IService
|
|
||||||
{
|
|
||||||
AddSingleton(obj);
|
|
||||||
obj.Install(this);
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
public T GetService<T>(bool required = true)
|
|
||||||
where T : class, IService
|
|
||||||
=> GetSingleton<T>(required);
|
|
||||||
|
|
||||||
private async Task OnReceivedEvent(WebSocketEventEventArgs e)
|
private async Task OnReceivedEvent(WebSocketEventEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -851,45 +816,99 @@ namespace Discord
|
|||||||
_sentInitialLog = true;
|
_sentInitialLog = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Async Wrapper
|
||||||
|
/// <summary> Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications. </summary>
|
||||||
|
public void Run(Func<Task> asyncAction)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
asyncAction().GetAwaiter().GetResult(); //Avoids creating AggregateExceptions
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException) { }
|
||||||
|
_disconnectedEvent.WaitOne();
|
||||||
|
}
|
||||||
|
/// <summary> Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications. </summary>
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
_disconnectedEvent.WaitOne();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
//Helpers
|
#region Services
|
||||||
/// <summary> Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications. </summary>
|
public T AddSingleton<T>(T obj)
|
||||||
public void Run(Func<Task> asyncAction)
|
where T : class
|
||||||
{
|
{
|
||||||
try
|
_singletons.Add(typeof(T), obj);
|
||||||
{
|
return obj;
|
||||||
asyncAction().GetAwaiter().GetResult(); //Avoids creating AggregateExceptions
|
}
|
||||||
}
|
public T GetSingleton<T>(bool required = true)
|
||||||
catch (TaskCanceledException) { }
|
where T : class
|
||||||
_disconnectedEvent.WaitOne();
|
{
|
||||||
}
|
object singleton;
|
||||||
/// <summary> Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications. </summary>
|
T singletonT = null;
|
||||||
public void Run()
|
if (_singletons.TryGetValue(typeof(T), out singleton))
|
||||||
{
|
singletonT = singleton as T;
|
||||||
_disconnectedEvent.WaitOne();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CheckReady()
|
if (singletonT == null && required)
|
||||||
{
|
throw new InvalidOperationException($"This operation requires {typeof(T).Name} to be added to {nameof(DiscordClient)}.");
|
||||||
switch (_state)
|
return singletonT;
|
||||||
{
|
}
|
||||||
case (int)DiscordClientState.Disconnecting:
|
public T AddService<T>(T obj)
|
||||||
throw new InvalidOperationException("The client is disconnecting.");
|
where T : class, IService
|
||||||
case (int)DiscordClientState.Disconnected:
|
{
|
||||||
throw new InvalidOperationException("The client is not connected to Discord");
|
AddSingleton(obj);
|
||||||
case (int)DiscordClientState.Connecting:
|
obj.Install(this);
|
||||||
throw new InvalidOperationException("The client is connecting.");
|
return obj;
|
||||||
}
|
}
|
||||||
}
|
public T GetService<T>(bool required = true)
|
||||||
|
where T : class, IService
|
||||||
public void GetCacheStats(out int serverCount, out int channelCount, out int userCount, out int uniqueUserCount, out int messageCount, out int roleCount)
|
=> GetSingleton<T>(required);
|
||||||
{
|
#endregion
|
||||||
serverCount = _servers.Count;
|
|
||||||
channelCount = _channels.Count;
|
#region IDisposable
|
||||||
userCount = _users.Count;
|
private bool _isDisposed = false;
|
||||||
uniqueUserCount = _globalUsers.Count;
|
|
||||||
messageCount = _messages.Count;
|
protected virtual void Dispose(bool isDisposing)
|
||||||
roleCount = _roles.Count;
|
{
|
||||||
}
|
if (!_isDisposed)
|
||||||
}
|
{
|
||||||
|
if (isDisposing)
|
||||||
|
{
|
||||||
|
_disconnectedEvent.Dispose();
|
||||||
|
_connectedEvent.Dispose();
|
||||||
|
}
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
//Helpers
|
||||||
|
private void CheckReady()
|
||||||
|
{
|
||||||
|
switch (_state)
|
||||||
|
{
|
||||||
|
case (int)DiscordClientState.Disconnecting:
|
||||||
|
throw new InvalidOperationException("The client is disconnecting.");
|
||||||
|
case (int)DiscordClientState.Disconnected:
|
||||||
|
throw new InvalidOperationException("The client is not connected to Discord");
|
||||||
|
case (int)DiscordClientState.Connecting:
|
||||||
|
throw new InvalidOperationException("The client is connecting.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetCacheStats(out int serverCount, out int channelCount, out int userCount, out int uniqueUserCount, out int messageCount, out int roleCount)
|
||||||
|
{
|
||||||
|
serverCount = _servers.Count;
|
||||||
|
channelCount = _channels.Count;
|
||||||
|
userCount = _users.Count;
|
||||||
|
uniqueUserCount = _globalUsers.Count;
|
||||||
|
messageCount = _messages.Count;
|
||||||
|
roleCount = _roles.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -42,6 +42,7 @@ namespace Discord
|
|||||||
public bool IsServerDeafened { get; private set; }
|
public bool IsServerDeafened { get; private set; }
|
||||||
public bool IsServerSuppressed { get; private set; }
|
public bool IsServerSuppressed { get; private set; }
|
||||||
public bool IsPrivate => _server.Id == null;
|
public bool IsPrivate => _server.Id == null;
|
||||||
|
public bool IsOwner => _server.Value.OwnerId == Id;
|
||||||
|
|
||||||
public string SessionId { get; private set; }
|
public string SessionId { get; private set; }
|
||||||
public string Token { get; private set; }
|
public string Token { get; private set; }
|
||||||
@@ -101,9 +102,23 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
if (_server.Id != null)
|
if (_server.Id != null)
|
||||||
{
|
{
|
||||||
return Server.Channels
|
if (_client.Config.UsePermissionsCache)
|
||||||
.Where(x => (x.Type == ChannelType.Text && x.GetPermissions(this).ReadMessages) ||
|
{
|
||||||
(x.Type == ChannelType.Voice && x.GetPermissions(this).Connect));
|
return Server.Channels
|
||||||
|
.Where(x => (x.Type == ChannelType.Text && x.GetPermissions(this).ReadMessages) ||
|
||||||
|
(x.Type == ChannelType.Voice && x.GetPermissions(this).Connect));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ChannelPermissions perms = new ChannelPermissions();
|
||||||
|
return Server.Channels
|
||||||
|
.Where(x =>
|
||||||
|
{
|
||||||
|
x.UpdatePermissions(this, perms);
|
||||||
|
return (x.Type == ChannelType.Text && perms.ReadMessages) ||
|
||||||
|
(x.Type == ChannelType.Voice && perms.Connect);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
namespace Discord.Net
|
namespace Discord.Net
|
||||||
{
|
{
|
||||||
|
#if NET45
|
||||||
|
[Serializable]
|
||||||
|
#endif
|
||||||
public class HttpException : Exception
|
public class HttpException : Exception
|
||||||
{
|
{
|
||||||
public HttpStatusCode StatusCode { get; }
|
public HttpStatusCode StatusCode { get; }
|
||||||
@@ -11,6 +15,10 @@ namespace Discord.Net
|
|||||||
: base($"The server responded with error {(int)statusCode} ({statusCode})")
|
: base($"The server responded with error {(int)statusCode} ({statusCode})")
|
||||||
{
|
{
|
||||||
StatusCode = statusCode;
|
StatusCode = statusCode;
|
||||||
}
|
}
|
||||||
}
|
#if NET45
|
||||||
|
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||||
|
=> base.GetObjectData(info, context);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
namespace Discord.Net
|
namespace Discord.Net
|
||||||
{
|
{
|
||||||
public sealed class TimeoutException : OperationCanceledException
|
#if NET45
|
||||||
|
[Serializable]
|
||||||
|
#endif
|
||||||
|
public sealed class TimeoutException : OperationCanceledException
|
||||||
{
|
{
|
||||||
public TimeoutException()
|
public TimeoutException()
|
||||||
: base("An operation has timed out.")
|
: base("An operation has timed out.")
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace Discord.Net.WebSockets
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class GatewayWebSocket
|
public partial class GatewaySocket
|
||||||
{
|
{
|
||||||
public event EventHandler<WebSocketEventEventArgs> ReceivedDispatch;
|
public event EventHandler<WebSocketEventEventArgs> ReceivedDispatch;
|
||||||
private void RaiseReceivedDispatch(string type, JToken payload)
|
private void RaiseReceivedDispatch(string type, JToken payload)
|
||||||
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Discord.Net.WebSockets
|
namespace Discord.Net.WebSockets
|
||||||
{
|
{
|
||||||
public partial class GatewayWebSocket : WebSocket
|
public partial class GatewaySocket : WebSocket
|
||||||
{
|
{
|
||||||
public int LastSequence => _lastSeq;
|
public int LastSequence => _lastSeq;
|
||||||
private int _lastSeq;
|
private int _lastSeq;
|
||||||
@@ -17,7 +17,7 @@ namespace Discord.Net.WebSockets
|
|||||||
public string SessionId => _sessionId;
|
public string SessionId => _sessionId;
|
||||||
private string _sessionId;
|
private string _sessionId;
|
||||||
|
|
||||||
public GatewayWebSocket(DiscordConfig config, Logger logger)
|
public GatewaySocket(DiscordConfig config, Logger logger)
|
||||||
: base(config, logger)
|
: base(config, logger)
|
||||||
{
|
{
|
||||||
Disconnected += async (s, e) =>
|
Disconnected += async (s, e) =>
|
||||||
@@ -28,14 +28,18 @@ namespace Discord.Net.WebSockets
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task Connect(string token)
|
public async Task Connect(string token)
|
||||||
{
|
{
|
||||||
_token = token;
|
await SignalDisconnect(wait: true).ConfigureAwait(false);
|
||||||
|
|
||||||
|
_token = token;
|
||||||
await BeginConnect().ConfigureAwait(false);
|
await BeginConnect().ConfigureAwait(false);
|
||||||
SendIdentify(token);
|
SendIdentify(token);
|
||||||
}
|
}
|
||||||
private async Task Redirect(string server)
|
private async Task Redirect(string server)
|
||||||
{
|
{
|
||||||
await BeginConnect().ConfigureAwait(false);
|
await SignalDisconnect(wait: true).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await BeginConnect().ConfigureAwait(false);
|
||||||
SendResume();
|
SendResume();
|
||||||
}
|
}
|
||||||
private async Task Reconnect()
|
private async Task Reconnect()
|
||||||
@@ -47,8 +51,8 @@ namespace Discord.Net.WebSockets
|
|||||||
while (!cancelToken.IsCancellationRequested)
|
while (!cancelToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Connect(_token).ConfigureAwait(false);
|
await Connect(_token).ConfigureAwait(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) { throw; }
|
catch (OperationCanceledException) { throw; }
|
||||||
@@ -114,7 +114,6 @@ namespace Discord.Net.WebSockets
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await SignalDisconnect(wait: true).ConfigureAwait(false);
|
|
||||||
_state = (int)WebSocketState.Connecting;
|
_state = (int)WebSocketState.Connecting;
|
||||||
|
|
||||||
if (ParentCancelToken == null)
|
if (ParentCancelToken == null)
|
||||||
@@ -173,7 +172,7 @@ namespace Discord.Net.WebSockets
|
|||||||
await Cleanup().ConfigureAwait(false);
|
await Cleanup().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!wait)
|
if (wait)
|
||||||
{
|
{
|
||||||
Task task = _runTask;
|
Task task = _runTask;
|
||||||
if (_runTask != null)
|
if (_runTask != null)
|
||||||
|
|||||||
1
src/Discord.Net/Settings.StyleCop
Normal file
1
src/Discord.Net/Settings.StyleCop
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<StyleCopSettings Version="105" />
|
||||||
@@ -28,9 +28,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Newtonsoft.Json": "7.0.1"
|
"Newtonsoft.Json": "7.0.1",
|
||||||
},
|
"StyleCop.Analyzers": "1.0.0-rc2"
|
||||||
|
},
|
||||||
|
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"net45": {
|
"net45": {
|
||||||
@@ -40,22 +41,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dotnet5.4": {
|
"dotnet5.4": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"System.Collections": "4.0.11-beta-23516",
|
"System.Collections": "4.0.11-beta-23516",
|
||||||
"System.Collections.Concurrent": "4.0.11-beta-23516",
|
"System.Collections.Concurrent": "4.0.11-beta-23516",
|
||||||
"System.Dynamic.Runtime": "4.0.11-beta-23516",
|
"System.Dynamic.Runtime": "4.0.11-beta-23516",
|
||||||
"System.IO.FileSystem": "4.0.1-beta-23516",
|
"System.IO.FileSystem": "4.0.1-beta-23516",
|
||||||
"System.IO.Compression": "4.1.0-beta-23516",
|
"System.IO.Compression": "4.1.0-beta-23516",
|
||||||
"System.Linq": "4.0.1-beta-23516",
|
"System.Linq": "4.0.1-beta-23516",
|
||||||
"System.Net.NameResolution": "4.0.0-beta-23516",
|
"System.Net.NameResolution": "4.0.0-beta-23516",
|
||||||
"System.Net.Sockets": "4.1.0-beta-23409",
|
"System.Net.Sockets": "4.1.0-beta-23409",
|
||||||
"System.Net.Requests": "4.0.11-beta-23516",
|
"System.Net.Requests": "4.0.11-beta-23516",
|
||||||
"System.Net.WebSockets.Client": "4.0.0-beta-23516",
|
"System.Net.WebSockets.Client": "4.0.0-beta-23516",
|
||||||
"System.Runtime.InteropServices": "4.0.21-beta-23516",
|
"System.Runtime.InteropServices": "4.0.21-beta-23516",
|
||||||
"System.Text.RegularExpressions": "4.0.11-beta-23516",
|
"System.Text.RegularExpressions": "4.0.11-beta-23516",
|
||||||
"System.Threading": "4.0.11-beta-23516",
|
"System.Threading": "4.0.11-beta-23516",
|
||||||
"System.Threading.Thread": "4.0.0-beta-23516"
|
"System.Threading.Thread": "4.0.0-beta-23516"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user