diff --git a/src/Discord.Net.Audio.Net5/Discord.Net.Audio.csproj b/src/Discord.Net.Audio.Net5/Discord.Net.Audio.csproj
index 326fb88ae..ce88c0890 100644
--- a/src/Discord.Net.Audio.Net5/Discord.Net.Audio.csproj
+++ b/src/Discord.Net.Audio.Net5/Discord.Net.Audio.csproj
@@ -67,18 +67,21 @@
Net\WebSockets\VoiceWebSocket.Events.cs
-
- Opus.cs
+
+ Opus\Enums.cs
-
- OpusDecoder.cs
+
+ Opus\OpusDecoder.cs
-
- OpusEncoder.cs
+
+ Opus\OpusEncoder.cs
Sodium.cs
+
+ Sodium\SecretBox.cs
+
VoiceBuffer.cs
diff --git a/src/Discord.Net.Audio/AudioService.cs b/src/Discord.Net.Audio/AudioService.cs
index 320bc4dee..74bc67c33 100644
--- a/src/Discord.Net.Audio/AudioService.cs
+++ b/src/Discord.Net.Audio/AudioService.cs
@@ -145,27 +145,27 @@ namespace Discord.Audio
return Task.FromResult(_defaultClient);
}
- var client = _voiceClients.GetOrAdd(server.Id, _ =>
+ var client = _voiceClients.GetOrAdd(server.Id, (Func)(_ =>
{
int id = unchecked(++_nextClientId);
var logger = Client.Log().CreateLogger($"Voice #{id}");
- GatewayWebSocket gatewaySocket = null;
+ Net.WebSockets.GatewaySocket gatewaySocket = null;
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);
voiceSocket.OnPacket += (s, e) =>
{
- RaiseOnPacket(e);
+ RaiseOnPacket(e);
};
voiceSocket.IsSpeaking += (s, e) =>
{
var user = Client.GetUser(server, e.UserId);
- RaiseUserIsSpeakingUpdated(user, e.IsSpeaking);
+ RaiseUserIsSpeakingUpdated(user, e.IsSpeaking);
};
return voiceClient;
- });
+ }));
//await client.Connect(gatewaySocket.Host, _client.Token).ConfigureAwait(false);
return Task.FromResult(client);
}
diff --git a/src/Discord.Net.Audio/AudioServiceConfig.cs b/src/Discord.Net.Audio/AudioServiceConfig.cs
index 421c28672..69c1a95b4 100644
--- a/src/Discord.Net.Audio/AudioServiceConfig.cs
+++ b/src/Discord.Net.Audio/AudioServiceConfig.cs
@@ -37,8 +37,12 @@ namespace Discord.Audio
public int? Bitrate { get { return _bitrate; } set { SetValue(ref _bitrate, value); } }
private int? _bitrate = null;
- //Lock
- protected bool _isLocked;
+ /// Gets or sets the number of channels (1 or 2) used for outgoing audio.
+ public int Channels { get { return _channels; } set { SetValue(ref _channels, value); } }
+ private int _channels = 1;
+
+ //Lock
+ protected bool _isLocked;
internal void Lock() { _isLocked = true; }
protected void SetValue(ref T storage, T value)
{
diff --git a/src/Discord.Net.Audio/DiscordAudioClient.cs b/src/Discord.Net.Audio/DiscordAudioClient.cs
index 791321188..eecbe0749 100644
--- a/src/Discord.Net.Audio/DiscordAudioClient.cs
+++ b/src/Discord.Net.Audio/DiscordAudioClient.cs
@@ -10,14 +10,14 @@ namespace Discord.Audio
public int Id => _id;
private readonly AudioService _service;
- private readonly GatewayWebSocket _gatewaySocket;
+ private readonly GatewaySocket _gatewaySocket;
private readonly VoiceWebSocket _voiceSocket;
private readonly Logger _logger;
public long? ServerId => _voiceSocket.ServerId;
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;
_id = id;
diff --git a/src/Discord.Net.Audio/Net/WebSockets/VoiceWebSocket.cs b/src/Discord.Net.Audio/Net/WebSockets/VoiceWebSocket.cs
index 93c16572c..1ccf3f8db 100644
--- a/src/Discord.Net.Audio/Net/WebSockets/VoiceWebSocket.cs
+++ b/src/Discord.Net.Audio/Net/WebSockets/VoiceWebSocket.cs
@@ -1,5 +1,7 @@
using Discord.API;
using Discord.Audio;
+using Discord.Audio.Opus;
+using Discord.Audio.Sodium;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
@@ -54,7 +56,7 @@ namespace Discord.Net.WebSockets
_targetAudioBufferLength = _audioConfig.BufferLength / 20; //20 ms frames
_encodingBuffer = new byte[MaxOpusSize];
_ssrcMapping = new ConcurrentDictionary();
- _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);
}
@@ -223,7 +225,7 @@ namespace Discord.Net.WebSockets
return;
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)
continue;
result = decodingBuffer;
@@ -294,7 +296,7 @@ namespace Discord.Net.WebSockets
if (_isEncrypted)
{
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)
throw new InvalidOperationException("Failed to encrypt ping packet");
pingPacket = new byte[pingPacket.Length + 16];
@@ -333,7 +335,7 @@ namespace Discord.Net.WebSockets
if (_isEncrypted)
{
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)
continue;
rtpPacketLength = encodedLength + 12 + 16;
diff --git a/src/Discord.Net.Audio/Opus.cs b/src/Discord.Net.Audio/Opus.cs
deleted file mode 100644
index 917047d05..000000000
--- a/src/Discord.Net.Audio/Opus.cs
+++ /dev/null
@@ -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
- }
-
- /// Supported coding modes.
- public enum Application : int
- {
- ///
- /// 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.
- ///
- Voip = 2048,
- ///
- /// 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.
- ///
- Audio = 2049,
- /// Low-delay mode that disables the speech-optimized mode in exchange for slightly reduced delay.
- Restricted_LowLatency = 2051
- }
-
- public enum Error : int
- {
- /// No error.
- OK = 0,
- /// One or more invalid/out of range arguments.
- BadArg = -1,
- /// The mode struct passed is invalid.
- BufferToSmall = -2,
- /// An internal error was detected.
- InternalError = -3,
- /// The compressed data passed is corrupted.
- InvalidPacket = -4,
- /// Invalid/unsupported request number.
- Unimplemented = -5,
- /// An encoder or decoder structure is invalid or already freed.
- InvalidState = -6,
- /// Memory allocation has failed.
- AllocFail = -7
- }
- }
-}
diff --git a/src/Discord.Net.Audio/Opus/Enums.cs b/src/Discord.Net.Audio/Opus/Enums.cs
new file mode 100644
index 000000000..2a705f746
--- /dev/null
+++ b/src/Discord.Net.Audio/Opus/Enums.cs
@@ -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
+ }
+
+ /// Supported coding modes.
+ internal enum OpusApplication : int
+ {
+ ///
+ /// 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.
+ ///
+ Voip = 2048,
+ ///
+ /// 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.
+ ///
+ Audio = 2049,
+ /// Low-delay mode that disables the speech-optimized mode in exchange for slightly reduced delay.
+ Restricted_LowLatency = 2051
+ }
+
+ internal enum OpusError : int
+ {
+ /// No error.
+ OK = 0,
+ /// One or more invalid/out of range arguments.
+ BadArg = -1,
+ /// The mode struct passed is invalid.
+ BufferToSmall = -2,
+ /// An internal error was detected.
+ InternalError = -3,
+ /// The compressed data passed is corrupted.
+ InvalidPacket = -4,
+ /// Invalid/unsupported request number.
+ Unimplemented = -5,
+ /// An encoder or decoder structure is invalid or already freed.
+ InvalidState = -6,
+ /// Memory allocation has failed.
+ AllocFail = -7
+ }
+
+}
diff --git a/src/Discord.Net.Audio/OpusDecoder.cs b/src/Discord.Net.Audio/Opus/OpusDecoder.cs
similarity index 57%
rename from src/Discord.Net.Audio/OpusDecoder.cs
rename to src/Discord.Net.Audio/Opus/OpusDecoder.cs
index e6c9cd400..fb5483327 100644
--- a/src/Discord.Net.Audio/OpusDecoder.cs
+++ b/src/Discord.Net.Audio/Opus/OpusDecoder.cs
@@ -1,11 +1,26 @@
using System;
+using System.Runtime.InteropServices;
+using System.Security;
-namespace Discord.Audio
+namespace Discord.Audio.Opus
{
/// Opus codec wrapper.
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;
/// Gets the bit rate of the encoder.
public const int BitRate = 16;
@@ -22,7 +37,7 @@ namespace Discord.Audio
/// Gets the bytes per frame.
public int FrameSize { get; private set; }
- /// Creates a new Opus encoder.
+ /// Creates a new Opus decoder.
/// Sampling rate of the input signal (Hz). Supported Values: 8000, 12000, 16000, 24000, or 48000.
/// Number of channels (1 or 2) in input signal.
/// Length, in milliseconds, that each frame takes. Supported Values: 2.5, 5, 10, 20, 40, 60
@@ -44,45 +59,32 @@ namespace Discord.Audio
SamplesPerFrame = samplingRate / 1000 * FrameLength;
FrameSize = SamplesPerFrame * SampleSize;
- Opus.Error error;
- _ptr = Opus.CreateDecoder(samplingRate, channels, out error);
- if (error != Opus.Error.OK)
+ OpusError error;
+ _ptr = UnsafeNativeMethods.CreateDecoder(samplingRate, channels, out error);
+ if (error != OpusError.OK)
throw new InvalidOperationException($"Error occured while creating decoder: {error}");
-
- SetForwardErrorCorrection(true);
}
- /// Produces Opus encoded audio from PCM samples.
- /// PCM samples to encode.
- /// Offset of the frame in pcmSamples.
- /// Buffer to store the encoded frame.
- /// Length of the frame contained in outputBuffer.
- public unsafe int DecodeFrame(byte[] input, int inputOffset, byte[] output)
+ /// Produces PCM samples from Opus-encoded audio.
+ /// PCM samples to decode.
+ /// Offset of the frame in input.
+ /// Buffer to store the decoded frame.
+ /// Length of the frame contained in output.
+ public unsafe int DecodeFrame(byte[] input, int inputOffset, byte[] output)
{
if (disposed)
throw new ObjectDisposedException(nameof(OpusDecoder));
int result = 0;
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)
- throw new Exception("Decoding failed: " + ((Opus.Error)result).ToString());
+ throw new Exception("Decoding failed: " + ((OpusError)result).ToString());
return result;
}
- /// Gets or sets whether Forward Error Correction is enabled.
- 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
+#region IDisposable
private bool disposed;
public void Dispose()
{
@@ -92,7 +94,7 @@ namespace Discord.Audio
GC.SuppressFinalize(this);
if (_ptr != IntPtr.Zero)
- Opus.DestroyEncoder(_ptr);
+ UnsafeNativeMethods.DestroyDecoder(_ptr);
disposed = true;
}
@@ -100,6 +102,6 @@ namespace Discord.Audio
{
Dispose();
}
- #endregion
+#endregion
}
}
\ No newline at end of file
diff --git a/src/Discord.Net.Audio/OpusEncoder.cs b/src/Discord.Net.Audio/Opus/OpusEncoder.cs
similarity index 67%
rename from src/Discord.Net.Audio/OpusEncoder.cs
rename to src/Discord.Net.Audio/Opus/OpusEncoder.cs
index fbb6a0729..a7f1b4ac1 100644
--- a/src/Discord.Net.Audio/OpusEncoder.cs
+++ b/src/Discord.Net.Audio/Opus/OpusEncoder.cs
@@ -1,11 +1,28 @@
using System;
+using System.Runtime.InteropServices;
+using System.Security;
-namespace Discord.Audio
+namespace Discord.Audio.Opus
{
/// Opus codec wrapper.
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;
/// Gets the bit rate of the encoder.
public const int BitsPerSample = 16;
@@ -24,7 +41,7 @@ namespace Discord.Audio
/// Gets the bit rate in kbit/s.
public int? BitRate { get; private set; }
/// Gets the coding mode of the encoder.
- public Opus.Application Application { get; private set; }
+ public OpusApplication Application { get; private set; }
/// Creates a new Opus encoder.
/// Sampling rate of the input signal (Hz). Supported Values: 8000, 12000, 16000, 24000, or 48000.
@@ -33,7 +50,7 @@ namespace Discord.Audio
/// Bitrate (kbit/s) used for this encoder. Supported Values: 1-512. Null will use the recommended bitrate.
/// Coding mode.
/// A new OpusEncoder
- 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 &&
samplingRate != 16000 && samplingRate != 24000 &&
@@ -53,9 +70,9 @@ namespace Discord.Audio
FrameSize = SamplesPerFrame * SampleSize;
BitRate = bitrate;
- Opus.Error error;
- _ptr = Opus.CreateEncoder(samplingRate, channels, (int)application, out error);
- if (error != Opus.Error.OK)
+ OpusError error;
+ _ptr = UnsafeNativeMethods.CreateEncoder(samplingRate, channels, (int)application, out error);
+ if (error != OpusError.OK)
throw new InvalidOperationException($"Error occured while creating encoder: {error}");
SetForwardErrorCorrection(true);
@@ -75,10 +92,10 @@ namespace Discord.Audio
int result = 0;
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)
- throw new Exception("Encoding failed: " + ((Opus.Error)result).ToString());
+ throw new Exception("Encoding failed: " + ((OpusError)result).ToString());
return result;
}
@@ -88,9 +105,9 @@ namespace Discord.Audio
if (disposed)
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)
- throw new Exception("Encoder error: " + ((Opus.Error)result).ToString());
+ throw new Exception("Encoder error: " + ((OpusError)result).ToString());
}
/// Gets or sets whether Forward Error Correction is enabled.
@@ -99,9 +116,9 @@ namespace Discord.Audio
if (disposed)
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)
- throw new Exception("Encoder error: " + ((Opus.Error)result).ToString());
+ throw new Exception("Encoder error: " + ((OpusError)result).ToString());
}
#region IDisposable
@@ -114,7 +131,7 @@ namespace Discord.Audio
GC.SuppressFinalize(this);
if (_ptr != IntPtr.Zero)
- Opus.DestroyEncoder(_ptr);
+ UnsafeNativeMethods.DestroyEncoder(_ptr);
disposed = true;
}
diff --git a/src/Discord.Net.Audio/Sodium.cs b/src/Discord.Net.Audio/Sodium.cs
index 9d9e6c297..7c98dffba 100644
--- a/src/Discord.Net.Audio/Sodium.cs
+++ b/src/Discord.Net.Audio/Sodium.cs
@@ -2,25 +2,4 @@
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);
- }
- }
}
diff --git a/src/Discord.Net.Audio/Sodium/SecretBox.cs b/src/Discord.Net.Audio/Sodium/SecretBox.cs
new file mode 100644
index 000000000..da2287d8b
--- /dev/null
+++ b/src/Discord.Net.Audio/Sodium/SecretBox.cs
@@ -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);
+ }
+ }
+}
diff --git a/src/Discord.Net.Commands/CommandService.Events.cs b/src/Discord.Net.Commands/CommandService.Events.cs
index f70223f14..c5ad7747b 100644
--- a/src/Discord.Net.Commands/CommandService.Events.cs
+++ b/src/Discord.Net.Commands/CommandService.Events.cs
@@ -2,7 +2,7 @@
namespace Discord.Commands
{
- public class CommandEventArgs
+ public class CommandEventArgs : EventArgs
{
private readonly string[] _args;
diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs
index b39c55d47..c856fb3b0 100644
--- a/src/Discord.Net.Commands/CommandService.cs
+++ b/src/Discord.Net.Commands/CommandService.cs
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace Discord.Commands
{
/// A Discord.Net client with extensions for handling common bot operations like text commands.
- public partial class CommandService : IService
+ public sealed partial class CommandService : IService
{
private const string DefaultPermissionError = "You do not have permission to access this command.";
diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj
index d5e5639f8..61f89a7a4 100644
--- a/src/Discord.Net.Net45/Discord.Net.csproj
+++ b/src/Discord.Net.Net45/Discord.Net.csproj
@@ -224,11 +224,11 @@
Net\TimeoutException.cs
-
- Net\WebSockets\GatewayWebSocket.cs
+
+ Net\WebSockets\GatewaySocket.cs
-
- Net\WebSockets\GatewayWebSockets.Events.cs
+
+ Net\WebSockets\GatewaySocket.Events.cs
Net\WebSockets\IWebSocketEngine.cs
diff --git a/src/Discord.Net/API/Messages/GatewaySocket.cs b/src/Discord.Net/API/Messages/GatewaySocket.cs
index d84fbc761..f890db0e9 100644
--- a/src/Discord.Net/API/Messages/GatewaySocket.cs
+++ b/src/Discord.Net/API/Messages/GatewaySocket.cs
@@ -9,165 +9,165 @@ using System.Collections.Generic;
namespace Discord.API
{
- public enum GatewayOpCodes : byte
- {
- /// Client <-- Server - Used to send most events.
- Dispatch = 0,
- /// Client <-> Server - Used to keep the connection alive and measure latency.
- Heartbeat = 1,
- /// Client --> Server - Used to associate a connection with a token and specify configuration.
- Identify = 2,
- /// Client --> Server - Used to update client's status and current game id.
- StatusUpdate = 3,
- /// Client --> Server - Used to join a particular voice channel.
- VoiceStateUpdate = 4,
- /// Client --> Server - Used to ensure the server's voice server is alive. Only send this if voice connection fails or suddenly drops.
- VoiceServerPing = 5,
- /// Client --> Server - Used to resume a connection after a redirect occurs.
- Resume = 6,
- /// Client <-- Server - Used to notify a client that they must reconnect to another gateway.
- Redirect = 7,
- /// Client --> Server - Used to request all members that were withheld by large_threshold
- RequestGuildMembers = 8
- }
+ public enum GatewayOpCodes : byte
+ {
+ /// Client <-- Server - Used to send most events.
+ Dispatch = 0,
+ /// Client <-> Server - Used to keep the connection alive and measure latency.
+ Heartbeat = 1,
+ /// Client --> Server - Used to associate a connection with a token and specify configuration.
+ Identify = 2,
+ /// Client --> Server - Used to update client's status and current game id.
+ StatusUpdate = 3,
+ /// Client --> Server - Used to join a particular voice channel.
+ VoiceStateUpdate = 4,
+ /// Client --> Server - Used to ensure the server's voice server is alive. Only send this if voice connection fails or suddenly drops.
+ VoiceServerPing = 5,
+ /// Client --> Server - Used to resume a connection after a redirect occurs.
+ Resume = 6,
+ /// Client <-- Server - Used to notify a client that they must reconnect to another gateway.
+ Redirect = 7,
+ /// Client --> Server - Used to request all members that were withheld by large_threshold
+ RequestGuildMembers = 8
+ }
- //Common
- public class WebSocketMessage
- {
- public WebSocketMessage() { }
- public WebSocketMessage(int op) { Operation = op; }
+ //Common
+ public class WebSocketMessage
+ {
+ public WebSocketMessage() { }
+ public WebSocketMessage(int op) { Operation = op; }
- [JsonProperty("op")]
- public int Operation;
- [JsonProperty("d")]
- public object Payload;
- [JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)]
- public string Type;
- [JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)]
- public int? Sequence;
- }
- public abstract class WebSocketMessage : WebSocketMessage
- where T : new()
- {
- public WebSocketMessage() { Payload = new T(); }
- public WebSocketMessage(int op) : base(op) { Payload = new T(); }
- public WebSocketMessage(int op, T payload) : base(op) { Payload = payload; }
+ [JsonProperty("op")]
+ public int Operation;
+ [JsonProperty("d")]
+ public object Payload;
+ [JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)]
+ public string Type;
+ [JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)]
+ public int? Sequence;
+ }
+ public abstract class WebSocketMessage : WebSocketMessage
+ where T : new()
+ {
+ public WebSocketMessage() { Payload = new T(); }
+ public WebSocketMessage(int op) : base(op) { Payload = new T(); }
+ public WebSocketMessage(int op, T payload) : base(op) { Payload = payload; }
- [JsonIgnore]
- public new T Payload
- {
- get
- {
- if (base.Payload is JToken)
- base.Payload = (base.Payload as JToken).ToObject();
- return (T)base.Payload;
- }
- set { base.Payload = value; }
- }
- }
+ [JsonIgnore]
+ public new T Payload
+ {
+ get
+ {
+ if (base.Payload is JToken)
+ base.Payload = (base.Payload as JToken).ToObject();
+ return (T)base.Payload;
+ }
+ set { base.Payload = value; }
+ }
+ }
- //Commands
- internal sealed class HeartbeatCommand : WebSocketMessage
- {
- public HeartbeatCommand() : base((int)GatewayOpCodes.Heartbeat, EpochTime.GetMilliseconds()) { }
- }
- internal sealed class IdentifyCommand : WebSocketMessage
- {
- public IdentifyCommand() : base((int)GatewayOpCodes.Identify) { }
- public class Data
- {
- [JsonProperty("token")]
- public string Token;
- [JsonProperty("v")]
- public int Version = 3;
- [JsonProperty("properties")]
- public Dictionary Properties = new Dictionary();
- [JsonProperty("large_threshold", NullValueHandling = NullValueHandling.Ignore)]
- public int? LargeThreshold;
- [JsonProperty("compress", NullValueHandling = NullValueHandling.Ignore)]
- public bool? Compress;
+ //Commands
+ internal sealed class HeartbeatCommand : WebSocketMessage
+ {
+ public HeartbeatCommand() : base((int)GatewayOpCodes.Heartbeat, EpochTime.GetMilliseconds()) { }
+ }
+ internal sealed class IdentifyCommand : WebSocketMessage
+ {
+ public IdentifyCommand() : base((int)GatewayOpCodes.Identify) { }
+ public class Data
+ {
+ [JsonProperty("token")]
+ public string Token;
+ [JsonProperty("v")]
+ public int Version = 3;
+ [JsonProperty("properties")]
+ public Dictionary Properties = new Dictionary();
+ [JsonProperty("large_threshold", NullValueHandling = NullValueHandling.Ignore)]
+ public int? LargeThreshold;
+ [JsonProperty("compress", NullValueHandling = NullValueHandling.Ignore)]
+ public bool? Compress;
}
- }
+ }
- internal sealed class StatusUpdateCommand : WebSocketMessage
- {
- public StatusUpdateCommand() : base((int)GatewayOpCodes.StatusUpdate) { }
- public class Data
- {
- [JsonProperty("idle_since")]
- public long? IdleSince;
- [JsonProperty("game_id")]
- public int? GameId;
- }
- }
+ internal sealed class StatusUpdateCommand : WebSocketMessage
+ {
+ public StatusUpdateCommand() : base((int)GatewayOpCodes.StatusUpdate) { }
+ public class Data
+ {
+ [JsonProperty("idle_since")]
+ public long? IdleSince;
+ [JsonProperty("game_id")]
+ public int? GameId;
+ }
+ }
- internal sealed class JoinVoiceCommand : WebSocketMessage
- {
- public JoinVoiceCommand() : base((int)GatewayOpCodes.VoiceStateUpdate) { }
- public class Data
- {
- [JsonProperty("guild_id")]
- [JsonConverter(typeof(LongStringConverter))]
- public long ServerId;
- [JsonProperty("channel_id")]
- [JsonConverter(typeof(LongStringConverter))]
- public long ChannelId;
- [JsonProperty("self_mute")]
- public string SelfMute;
- [JsonProperty("self_deaf")]
- public string SelfDeaf;
- }
- }
+ internal sealed class JoinVoiceCommand : WebSocketMessage
+ {
+ public JoinVoiceCommand() : base((int)GatewayOpCodes.VoiceStateUpdate) { }
+ public class Data
+ {
+ [JsonProperty("guild_id")]
+ [JsonConverter(typeof(LongStringConverter))]
+ public long ServerId;
+ [JsonProperty("channel_id")]
+ [JsonConverter(typeof(LongStringConverter))]
+ public long ChannelId;
+ [JsonProperty("self_mute")]
+ public string SelfMute;
+ [JsonProperty("self_deaf")]
+ public string SelfDeaf;
+ }
+ }
- internal sealed class ResumeCommand : WebSocketMessage
- {
- public ResumeCommand() : base((int)GatewayOpCodes.Resume) { }
- public class Data
- {
- [JsonProperty("session_id")]
- public string SessionId;
- [JsonProperty("seq")]
- public int Sequence;
- }
- }
+ internal sealed class ResumeCommand : WebSocketMessage
+ {
+ public ResumeCommand() : base((int)GatewayOpCodes.Resume) { }
+ public class Data
+ {
+ [JsonProperty("session_id")]
+ public string SessionId;
+ [JsonProperty("seq")]
+ public int Sequence;
+ }
+ }
- //Events
- internal sealed class ReadyEvent
- {
- public sealed class ReadStateInfo
- {
- [JsonProperty("id")]
- public string ChannelId;
- [JsonProperty("mention_count")]
- public int MentionCount;
- [JsonProperty("last_message_id")]
- public string LastMessageId;
- }
+ //Events
+ internal sealed class ReadyEvent
+ {
+ public sealed class ReadStateInfo
+ {
+ [JsonProperty("id")]
+ public string ChannelId;
+ [JsonProperty("mention_count")]
+ public int MentionCount;
+ [JsonProperty("last_message_id")]
+ public string LastMessageId;
+ }
- [JsonProperty("v")]
- public int Version;
- [JsonProperty("user")]
- public UserInfo User;
- [JsonProperty("session_id")]
- public string SessionId;
- [JsonProperty("read_state")]
- public ReadStateInfo[] ReadState;
- [JsonProperty("guilds")]
- public ExtendedGuildInfo[] Guilds;
- [JsonProperty("private_channels")]
- public ChannelInfo[] PrivateChannels;
- [JsonProperty("heartbeat_interval")]
- public int HeartbeatInterval;
- }
+ [JsonProperty("v")]
+ public int Version;
+ [JsonProperty("user")]
+ public UserInfo User;
+ [JsonProperty("session_id")]
+ public string SessionId;
+ [JsonProperty("read_state")]
+ public ReadStateInfo[] ReadState;
+ [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;
- }
+ internal sealed class RedirectEvent
+ {
+ [JsonProperty("url")]
+ public string Url;
+ }
+ internal sealed class ResumeEvent
+ {
+ [JsonProperty("heartbeat_interval")]
+ public int HeartbeatInterval;
+ }
}
diff --git a/src/Discord.Net/DiscordClient.Users.cs b/src/Discord.Net/DiscordClient.Users.cs
index fbe7a572f..f6d4ed30b 100644
--- a/src/Discord.Net/DiscordClient.Users.cs
+++ b/src/Discord.Net/DiscordClient.Users.cs
@@ -58,7 +58,7 @@ namespace Discord
}
}
- public partial class DiscordClient
+ public partial class DiscordClient : IDisposable
{
public event EventHandler UserJoined;
private void RaiseUserJoined(User user)
@@ -305,5 +305,5 @@ namespace Discord
_webSocket.SendStatusUpdate(_status == UserStatus.Idle ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (long?)null, _gameId);
return TaskHelper.CompletedTask;
}
- }
+ }
}
\ No newline at end of file
diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs
index 4c4c82866..f8a782ebe 100644
--- a/src/Discord.Net/DiscordClient.cs
+++ b/src/Discord.Net/DiscordClient.cs
@@ -4,7 +4,6 @@ using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.Linq;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Threading;
@@ -82,8 +81,8 @@ namespace Discord
private readonly DiscordAPIClient _api;
/// Returns the internal websocket object.
- public GatewayWebSocket WebSocket => _webSocket;
- private readonly GatewayWebSocket _webSocket;
+ public GatewaySocket WebSocket => _webSocket;
+ private readonly GatewaySocket _webSocket;
public string GatewayUrl => _gateway;
private string _gateway;
@@ -140,11 +139,24 @@ namespace Discord
CreateCacheLogger();
//Networking
- _webSocket = CreateWebSocket();
- _api = new DiscordAPIClient(_config);
+ _webSocket = new GatewaySocket(_config, _log.CreateLogger("WebSocket"));
+ 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)
_pendingMessages = new ConcurrentQueue();
- this.Connected += async (s, e) =>
+ Connected += async (s, e) =>
{
_api.CancelToken = _cancelToken;
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;
- }
-
/// Connects to the Discord server with the provided email and password.
/// Returns a token for future connections.
public async Task Connect(string email, string password)
@@ -285,73 +279,73 @@ namespace Discord
if (State != DiscordClientState.Disconnected)
await Disconnect().ConfigureAwait(false);
- string token;
- try
- {
- var response = await _api.Login(email, password)
- .ConfigureAwait(false);
- token = response.Token;
- if (_config.LogLevel >= LogSeverity.Verbose)
- _logger.Log(LogSeverity.Verbose, "Login successful, got token.");
-
- await Connect(token);
- return token;
- }
- catch (TaskCanceledException) { throw new TimeoutException(); }
- }
+ var response = await _api.Login(email, password)
+ .ConfigureAwait(false);
+ _token = response.Token;
+ _api.Token = response.Token;
+ if (_config.LogLevel >= LogSeverity.Verbose)
+ _logger.Log(LogSeverity.Verbose, "Login successful, got token.");
+
+ await BeginConnect();
+ return response.Token;
+ }
/// Connects to the Discord server with the provided token.
public async Task Connect(string token)
- {
- if (!_sentInitialLog)
- SendInitialLog();
-
- if (State != (int)DiscordClientState.Disconnected)
- await Disconnect().ConfigureAwait(false);
+ {
+ if (!_sentInitialLog)
+ SendInitialLog();
- _api.Token = token;
- var gatewayResponse = await _api.Gateway().ConfigureAwait(false);
- string gateway = gatewayResponse.Url;
- if (_config.LogLevel >= LogSeverity.Verbose)
- _logger.Log(LogSeverity.Verbose, $"Websocket endpoint: {gateway}");
-
- try
- {
- _state = (int)DiscordClientState.Connecting;
- _disconnectedEvent.Reset();
-
- _gateway = gateway;
- _token = token;
+ if (State != (int)DiscordClientState.Disconnected)
+ await Disconnect().ConfigureAwait(false);
- _cancelTokenSource = new CancellationTokenSource();
- _cancelToken = _cancelTokenSource.Token;
-
- _webSocket.Host = gateway;
- _webSocket.ParentCancelToken = _cancelToken;
- await _webSocket.Connect(token).ConfigureAwait(false);
-
- _runTask = RunTasks();
-
- try
- {
- //Cancel if either Disconnect is called, data socket errors or timeout is reached
- 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;
- }
+ _token = token;
+ _api.Token = token;
+ await BeginConnect();
+ }
- //_state = (int)DiscordClientState.Connected;
- }
- catch
- {
- await Disconnect().ConfigureAwait(false);
- throw;
- }
- }
- private void CompleteConnect()
+ private async Task BeginConnect()
+ {
+ try
+ {
+ _state = (int)DiscordClientState.Connecting;
+
+ var gatewayResponse = await _api.Gateway().ConfigureAwait(false);
+ string gateway = gatewayResponse.Url;
+ if (_config.LogLevel >= LogSeverity.Verbose)
+ _logger.Log(LogSeverity.Verbose, $"Websocket endpoint: {gateway}");
+
+ _disconnectedEvent.Reset();
+
+ _gateway = gateway;
+
+ _cancelTokenSource = new CancellationTokenSource();
+ _cancelToken = _cancelTokenSource.Token;
+
+ _webSocket.Host = gateway;
+ _webSocket.ParentCancelToken = _cancelToken;
+ await _webSocket.Connect(_token).ConfigureAwait(false);
+
+ _runTask = RunTasks();
+
+ try
+ {
+ //Cancel if either Disconnect is called, data socket errors or timeout is reached
+ 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;
+ }
+ }
+ catch
+ {
+ await Disconnect().ConfigureAwait(false);
+ throw;
+ }
+ }
+ private void EndConnect()
{
_state = (int)DiscordClientState.Connected;
_connectedEvent.Set();
@@ -359,8 +353,8 @@ namespace Discord
}
/// Disconnects from the Discord server, canceling any pending requests.
- public Task Disconnect() => DisconnectInternal(new Exception("Disconnect was requested by user."), isUnexpected: false);
- private async Task DisconnectInternal(Exception ex = null, bool isUnexpected = true, bool skipAwait = false)
+ public Task Disconnect() => SignalDisconnect(new Exception("Disconnect was requested by user."), isUnexpected: false);
+ private async Task SignalDisconnect(Exception ex = null, bool isUnexpected = true, bool wait = false)
{
int oldState;
bool hasWriterLock;
@@ -386,7 +380,7 @@ namespace Discord
await Cleanup().ConfigureAwait(false);*/
}
- if (!skipAwait)
+ if (wait)
{
Task task = _runTask;
if (_runTask != null)
@@ -407,10 +401,10 @@ namespace Discord
//Wait until the first task ends/errors and capture the error
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.
- await DisconnectInternal(skipAwait: true).ConfigureAwait(false);
+ await SignalDisconnect(wait: true).ConfigureAwait(false);
//Wait for the remaining tasks to complete
try { await allTasks.ConfigureAwait(false); }
@@ -453,35 +447,6 @@ namespace Discord
_privateUser = null;
}
-
- public T AddSingleton(T obj)
- where T : class
- {
- _singletons.Add(typeof(T), obj);
- return obj;
- }
- public T GetSingleton(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 obj)
- where T : class, IService
- {
- AddSingleton(obj);
- obj.Install(this);
- return obj;
- }
- public T GetService(bool required = true)
- where T : class, IService
- => GetSingleton(required);
private async Task OnReceivedEvent(WebSocketEventEventArgs e)
{
@@ -851,45 +816,99 @@ namespace Discord
_sentInitialLog = true;
}
+ #region Async Wrapper
+ /// Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications.
+ public void Run(Func asyncAction)
+ {
+ try
+ {
+ asyncAction().GetAwaiter().GetResult(); //Avoids creating AggregateExceptions
+ }
+ catch (TaskCanceledException) { }
+ _disconnectedEvent.WaitOne();
+ }
+ /// Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications.
+ public void Run()
+ {
+ _disconnectedEvent.WaitOne();
+ }
+ #endregion
+
+ #region Services
+ public T AddSingleton(T obj)
+ where T : class
+ {
+ _singletons.Add(typeof(T), obj);
+ return obj;
+ }
+ public T GetSingleton(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 obj)
+ where T : class, IService
+ {
+ AddSingleton(obj);
+ obj.Install(this);
+ return obj;
+ }
+ public T GetService(bool required = true)
+ where T : class, IService
+ => GetSingleton(required);
+ #endregion
+
+ #region IDisposable
+ private bool _isDisposed = false;
+
+ protected virtual void Dispose(bool isDisposing)
+ {
+ 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.");
+ }
+ }
- //Helpers
- /// Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications.
- public void Run(Func asyncAction)
- {
- try
- {
- asyncAction().GetAwaiter().GetResult(); //Avoids creating AggregateExceptions
- }
- catch (TaskCanceledException) { }
- _disconnectedEvent.WaitOne();
- }
- /// Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications.
- public void Run()
- {
- _disconnectedEvent.WaitOne();
- }
-
- 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;
- }
- }
+ 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;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Discord.Net/Models/User.cs b/src/Discord.Net/Models/User.cs
index 80e7a19eb..924166529 100644
--- a/src/Discord.Net/Models/User.cs
+++ b/src/Discord.Net/Models/User.cs
@@ -42,6 +42,7 @@ namespace Discord
public bool IsServerDeafened { get; private set; }
public bool IsServerSuppressed { get; private set; }
public bool IsPrivate => _server.Id == null;
+ public bool IsOwner => _server.Value.OwnerId == Id;
public string SessionId { get; private set; }
public string Token { get; private set; }
@@ -101,9 +102,23 @@ namespace Discord
{
if (_server.Id != null)
{
- return Server.Channels
- .Where(x => (x.Type == ChannelType.Text && x.GetPermissions(this).ReadMessages) ||
- (x.Type == ChannelType.Voice && x.GetPermissions(this).Connect));
+ if (_client.Config.UsePermissionsCache)
+ {
+ 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
{
diff --git a/src/Discord.Net/Net/HttpException.cs b/src/Discord.Net/Net/HttpException.cs
index 84ae66530..192136c1a 100644
--- a/src/Discord.Net/Net/HttpException.cs
+++ b/src/Discord.Net/Net/HttpException.cs
@@ -1,8 +1,12 @@
using System;
using System.Net;
+using System.Runtime.Serialization;
namespace Discord.Net
{
+#if NET45
+ [Serializable]
+#endif
public class HttpException : Exception
{
public HttpStatusCode StatusCode { get; }
@@ -11,6 +15,10 @@ namespace Discord.Net
: base($"The server responded with error {(int)statusCode} ({statusCode})")
{
StatusCode = statusCode;
- }
- }
+ }
+#if NET45
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ => base.GetObjectData(info, context);
+#endif
+ }
}
diff --git a/src/Discord.Net/Net/TimeoutException.cs b/src/Discord.Net/Net/TimeoutException.cs
index 090d1e5d4..c0385015a 100644
--- a/src/Discord.Net/Net/TimeoutException.cs
+++ b/src/Discord.Net/Net/TimeoutException.cs
@@ -2,7 +2,10 @@
namespace Discord.Net
{
- public sealed class TimeoutException : OperationCanceledException
+#if NET45
+ [Serializable]
+#endif
+ public sealed class TimeoutException : OperationCanceledException
{
public TimeoutException()
: base("An operation has timed out.")
diff --git a/src/Discord.Net/Net/WebSockets/GatewayWebSockets.Events.cs b/src/Discord.Net/Net/WebSockets/GatewaySocket.Events.cs
similarity index 93%
rename from src/Discord.Net/Net/WebSockets/GatewayWebSockets.Events.cs
rename to src/Discord.Net/Net/WebSockets/GatewaySocket.Events.cs
index 78f3c1ac5..a47f8231c 100644
--- a/src/Discord.Net/Net/WebSockets/GatewayWebSockets.Events.cs
+++ b/src/Discord.Net/Net/WebSockets/GatewaySocket.Events.cs
@@ -14,7 +14,7 @@ namespace Discord.Net.WebSockets
}
}
- public partial class GatewayWebSocket
+ public partial class GatewaySocket
{
public event EventHandler ReceivedDispatch;
private void RaiseReceivedDispatch(string type, JToken payload)
diff --git a/src/Discord.Net/Net/WebSockets/GatewayWebSocket.cs b/src/Discord.Net/Net/WebSockets/GatewaySocket.cs
similarity index 90%
rename from src/Discord.Net/Net/WebSockets/GatewayWebSocket.cs
rename to src/Discord.Net/Net/WebSockets/GatewaySocket.cs
index 441de8f40..aaa79a0aa 100644
--- a/src/Discord.Net/Net/WebSockets/GatewayWebSocket.cs
+++ b/src/Discord.Net/Net/WebSockets/GatewaySocket.cs
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace Discord.Net.WebSockets
{
- public partial class GatewayWebSocket : WebSocket
+ public partial class GatewaySocket : WebSocket
{
public int LastSequence => _lastSeq;
private int _lastSeq;
@@ -17,7 +17,7 @@ namespace Discord.Net.WebSockets
public string SessionId => _sessionId;
private string _sessionId;
- public GatewayWebSocket(DiscordConfig config, Logger logger)
+ public GatewaySocket(DiscordConfig config, Logger logger)
: base(config, logger)
{
Disconnected += async (s, e) =>
@@ -28,14 +28,18 @@ namespace Discord.Net.WebSockets
}
public async Task Connect(string token)
- {
- _token = token;
+ {
+ await SignalDisconnect(wait: true).ConfigureAwait(false);
+
+ _token = token;
await BeginConnect().ConfigureAwait(false);
SendIdentify(token);
}
private async Task Redirect(string server)
- {
- await BeginConnect().ConfigureAwait(false);
+ {
+ await SignalDisconnect(wait: true).ConfigureAwait(false);
+
+ await BeginConnect().ConfigureAwait(false);
SendResume();
}
private async Task Reconnect()
@@ -47,8 +51,8 @@ namespace Discord.Net.WebSockets
while (!cancelToken.IsCancellationRequested)
{
try
- {
- await Connect(_token).ConfigureAwait(false);
+ {
+ await Connect(_token).ConfigureAwait(false);
break;
}
catch (OperationCanceledException) { throw; }
diff --git a/src/Discord.Net/Net/WebSockets/WebSocket.cs b/src/Discord.Net/Net/WebSockets/WebSocket.cs
index b0352c067..ea76037b2 100644
--- a/src/Discord.Net/Net/WebSockets/WebSocket.cs
+++ b/src/Discord.Net/Net/WebSockets/WebSocket.cs
@@ -114,7 +114,6 @@ namespace Discord.Net.WebSockets
{
try
{
- await SignalDisconnect(wait: true).ConfigureAwait(false);
_state = (int)WebSocketState.Connecting;
if (ParentCancelToken == null)
@@ -173,7 +172,7 @@ namespace Discord.Net.WebSockets
await Cleanup().ConfigureAwait(false);
}
- if (!wait)
+ if (wait)
{
Task task = _runTask;
if (_runTask != null)
diff --git a/src/Discord.Net/Settings.StyleCop b/src/Discord.Net/Settings.StyleCop
new file mode 100644
index 000000000..bb05f99bc
--- /dev/null
+++ b/src/Discord.Net/Settings.StyleCop
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json
index 59c5573aa..b0d08bf04 100644
--- a/src/Discord.Net/project.json
+++ b/src/Discord.Net/project.json
@@ -28,9 +28,10 @@
}
},
- "dependencies": {
- "Newtonsoft.Json": "7.0.1"
- },
+ "dependencies": {
+ "Newtonsoft.Json": "7.0.1",
+ "StyleCop.Analyzers": "1.0.0-rc2"
+ },
"frameworks": {
"net45": {
@@ -40,22 +41,22 @@
}
},
"dotnet5.4": {
- "dependencies": {
- "System.Collections": "4.0.11-beta-23516",
- "System.Collections.Concurrent": "4.0.11-beta-23516",
- "System.Dynamic.Runtime": "4.0.11-beta-23516",
- "System.IO.FileSystem": "4.0.1-beta-23516",
- "System.IO.Compression": "4.1.0-beta-23516",
- "System.Linq": "4.0.1-beta-23516",
- "System.Net.NameResolution": "4.0.0-beta-23516",
- "System.Net.Sockets": "4.1.0-beta-23409",
- "System.Net.Requests": "4.0.11-beta-23516",
- "System.Net.WebSockets.Client": "4.0.0-beta-23516",
- "System.Runtime.InteropServices": "4.0.21-beta-23516",
- "System.Text.RegularExpressions": "4.0.11-beta-23516",
- "System.Threading": "4.0.11-beta-23516",
- "System.Threading.Thread": "4.0.0-beta-23516"
- }
+ "dependencies": {
+ "System.Collections": "4.0.11-beta-23516",
+ "System.Collections.Concurrent": "4.0.11-beta-23516",
+ "System.Dynamic.Runtime": "4.0.11-beta-23516",
+ "System.IO.FileSystem": "4.0.1-beta-23516",
+ "System.IO.Compression": "4.1.0-beta-23516",
+ "System.Linq": "4.0.1-beta-23516",
+ "System.Net.NameResolution": "4.0.0-beta-23516",
+ "System.Net.Sockets": "4.1.0-beta-23409",
+ "System.Net.Requests": "4.0.11-beta-23516",
+ "System.Net.WebSockets.Client": "4.0.0-beta-23516",
+ "System.Runtime.InteropServices": "4.0.21-beta-23516",
+ "System.Text.RegularExpressions": "4.0.11-beta-23516",
+ "System.Threading": "4.0.11-beta-23516",
+ "System.Threading.Thread": "4.0.0-beta-23516"
+ }
}
}
}
\ No newline at end of file