@@ -2,12 +2,17 @@ | |||||
namespace Discord.Audio | namespace Discord.Audio | ||||
{ | { | ||||
/// <summary> Specifies an audio mode for Discord. </summary> | |||||
[Flags] | [Flags] | ||||
public enum AudioMode : byte | public enum AudioMode : byte | ||||
{ | { | ||||
/// <summary> Audio send/receive is disabled. </summary> | |||||
Disabled = 0, | Disabled = 0, | ||||
/// <summary> Audio can only be broadcasted by the client. </summary> | |||||
Outgoing = 1, | Outgoing = 1, | ||||
/// <summary> Audio can only be received by the client. </summary> | |||||
Incoming = 2, | Incoming = 2, | ||||
/// <summary> Audio can be sent and received by the client. </summary> | |||||
Both = Outgoing | Incoming | Both = Outgoing | Incoming | ||||
} | } | ||||
} | } |
@@ -5,19 +5,26 @@ namespace Discord.Audio | |||||
{ | { | ||||
public interface IAudioClient | public interface IAudioClient | ||||
{ | { | ||||
/// <summary> Fired when the client connects to Discord. </summary> | |||||
event Func<Task> Connected; | event Func<Task> Connected; | ||||
/// <summary> Fired when the client disconnects from Discord. </summary> | |||||
event Func<Exception, Task> Disconnected; | event Func<Exception, Task> Disconnected; | ||||
/// <summary> Fired in response to a heartbeat, providing the old and new latency. </summary> | |||||
event Func<int, int, Task> LatencyUpdated; | event Func<int, int, Task> LatencyUpdated; | ||||
/// <summary> Gets the API client used for communicating with Discord. </summary> | |||||
DiscordVoiceAPIClient ApiClient { get; } | DiscordVoiceAPIClient ApiClient { get; } | ||||
/// <summary> Gets the current connection state of this client. </summary> | /// <summary> Gets the current connection state of this client. </summary> | ||||
ConnectionState ConnectionState { get; } | ConnectionState ConnectionState { get; } | ||||
/// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | /// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | ||||
int Latency { get; } | int Latency { get; } | ||||
/// <summary> Disconnects the current client from Discord. </summary> | |||||
Task DisconnectAsync(); | Task DisconnectAsync(); | ||||
/// <summary> Creates an Opus stream for sending raw Opus-encoded data. </summary> | |||||
RTPWriteStream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000); | RTPWriteStream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000); | ||||
/// <summary> Creates a PCM stream for sending unencoded PCM data. </summary> | |||||
OpusEncodeStream CreatePCMStream(int samplesPerFrame, int? bitrate = null, OpusApplication application = OpusApplication.MusicOrMixed, int bufferSize = 4000); | OpusEncodeStream CreatePCMStream(int samplesPerFrame, int? bitrate = null, OpusApplication application = OpusApplication.MusicOrMixed, int bufferSize = 4000); | ||||
} | } | ||||
} | } |
@@ -1,9 +1,16 @@ | |||||
namespace Discord.Audio | namespace Discord.Audio | ||||
{ | { | ||||
/// <summary> The types of encoding which Opus supports during encoding. </summary> | |||||
public enum OpusApplication : int | public enum OpusApplication : int | ||||
{ | { | ||||
/// <summary> Specifies that the <see cref="Discord.Audio.IAudioClient"/> uses | |||||
/// encoding to improve the quality of voice communication. </summary> | |||||
Voice = 2048, | Voice = 2048, | ||||
/// <summary> Specifies that the <see cref="Discord.Audio.IAudioClient"/> uses | |||||
/// encoding to improve the overall quality of mixed-media audio transmission. </summary> | |||||
MusicOrMixed = 2049, | MusicOrMixed = 2049, | ||||
/// <summary> Specifies that the <see cref="Discord.Audio.IAudioClient"/> uses | |||||
/// encoding to reduce overall latency. </summary> | |||||
LowLatency = 2051 | LowLatency = 2051 | ||||
} | } | ||||
} | } |
@@ -24,7 +24,9 @@ namespace Discord.Audio | |||||
/// <summary> Produces PCM samples from Opus-encoded audio. </summary> | /// <summary> Produces PCM samples from Opus-encoded audio. </summary> | ||||
/// <param name="input">PCM samples to decode.</param> | /// <param name="input">PCM samples to decode.</param> | ||||
/// <param name="inputOffset">Offset of the frame in input.</param> | /// <param name="inputOffset">Offset of the frame in input.</param> | ||||
/// <param name="inputCount">Number of bytes of the frame in input.</param> | |||||
/// <param name="output">Buffer to store the decoded frame.</param> | /// <param name="output">Buffer to store the decoded frame.</param> | ||||
/// <param name="outputOffset">Zero-based offset for the output.</param> | |||||
public unsafe int DecodeFrame(byte[] input, int inputOffset, int inputCount, byte[] output, int outputOffset) | public unsafe int DecodeFrame(byte[] input, int inputOffset, int inputCount, byte[] output, int outputOffset) | ||||
{ | { | ||||
int result = 0; | int result = 0; | ||||
@@ -31,6 +31,9 @@ namespace Discord.Audio | |||||
/// <summary> Produces Opus encoded audio from PCM samples. </summary> | /// <summary> Produces Opus encoded audio from PCM samples. </summary> | ||||
/// <param name="input">PCM samples to encode.</param> | /// <param name="input">PCM samples to encode.</param> | ||||
/// <param name="output">Buffer to store the encoded frame.</param> | /// <param name="output">Buffer to store the encoded frame.</param> | ||||
/// <param name="inputOffset">Offset of the frame in input.</param> | |||||
/// <param name="inputCount">Number of bytes of the frame in input.</param> | |||||
/// <param name="outputOffset">Zero-based offset for the output.</param> | |||||
/// <returns>Length of the frame contained in outputBuffer.</returns> | /// <returns>Length of the frame contained in outputBuffer.</returns> | ||||
public unsafe int EncodeFrame(byte[] input, int inputOffset, int inputCount, byte[] output, int outputOffset) | public unsafe int EncodeFrame(byte[] input, int inputOffset, int inputCount, byte[] output, int outputOffset) | ||||
{ | { | ||||
@@ -3,13 +3,14 @@ using System.Runtime.InteropServices; | |||||
namespace Discord.Audio | namespace Discord.Audio | ||||
{ | { | ||||
public unsafe static class SecretBox | |||||
public unsafe static class SecretBox // TODO: should this be public? | |||||
{ | { | ||||
[DllImport("libsodium", EntryPoint = "crypto_secretbox_easy", CallingConvention = CallingConvention.Cdecl)] | [DllImport("libsodium", EntryPoint = "crypto_secretbox_easy", CallingConvention = CallingConvention.Cdecl)] | ||||
private static extern int SecretBoxEasy(byte* output, byte* input, long inputLength, byte[] nonce, byte[] secret); | private static extern int SecretBoxEasy(byte* output, byte* input, long inputLength, byte[] nonce, byte[] secret); | ||||
[DllImport("libsodium", EntryPoint = "crypto_secretbox_open_easy", CallingConvention = CallingConvention.Cdecl)] | [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); | private static extern int SecretBoxOpenEasy(byte* output, byte* input, long inputLength, byte[] nonce, byte[] secret); | ||||
/// <summary> Encrypts a payload with the given nonce and secret. </summary> | |||||
public static int Encrypt(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret) | public static int Encrypt(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret) | ||||
{ | { | ||||
fixed (byte* inPtr = input) | fixed (byte* inPtr = input) | ||||
@@ -21,6 +22,7 @@ namespace Discord.Audio | |||||
return inputLength + 16; | return inputLength + 16; | ||||
} | } | ||||
} | } | ||||
/// <summary> Decrypts a payload with the given nonce and secret. </summary> | |||||
public static int Decrypt(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret) | public static int Decrypt(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret) | ||||
{ | { | ||||
fixed (byte* inPtr = input) | fixed (byte* inPtr = input) | ||||
@@ -1,5 +1,6 @@ | |||||
namespace Discord.Audio | namespace Discord.Audio | ||||
{ | { | ||||
/// <summary> A stream which decodes Opus frames as they are read. </summary> | |||||
public class OpusDecodeStream : RTPReadStream | public class OpusDecodeStream : RTPReadStream | ||||
{ | { | ||||
private readonly byte[] _buffer; | private readonly byte[] _buffer; | ||||
@@ -13,12 +14,14 @@ | |||||
_decoder = new OpusDecoder(samplingRate, channels); | _decoder = new OpusDecoder(samplingRate, channels); | ||||
} | } | ||||
/// <summary> Reads Opus-encoded frame from the stream, filling the buffer with PCM data </summary> | |||||
public override int Read(byte[] buffer, int offset, int count) | public override int Read(byte[] buffer, int offset, int count) | ||||
{ | { | ||||
count = _decoder.DecodeFrame(buffer, offset, count, _buffer, 0); | count = _decoder.DecodeFrame(buffer, offset, count, _buffer, 0); | ||||
return base.Read(_buffer, 0, count); | return base.Read(_buffer, 0, count); | ||||
} | } | ||||
/// <inheritdoc/> | |||||
protected override void Dispose(bool disposing) | protected override void Dispose(bool disposing) | ||||
{ | { | ||||
base.Dispose(disposing); | base.Dispose(disposing); | ||||
@@ -1,8 +1,11 @@ | |||||
namespace Discord.Audio | namespace Discord.Audio | ||||
{ | { | ||||
/// <summary> A stream which encodes Opus frames as raw PCM data is written. </summary> | |||||
public class OpusEncodeStream : RTPWriteStream | public class OpusEncodeStream : RTPWriteStream | ||||
{ | { | ||||
public int SampleRate = 48000; | |||||
/// <summary> The sample rate of the Opus stream. </summary> | |||||
public int SampleRate = 48000; // TODO: shouldn't these be readonly? | |||||
/// <summary> The number of channels of the Opus stream. </summary> | |||||
public int Channels = 2; | public int Channels = 2; | ||||
private readonly OpusEncoder _encoder; | private readonly OpusEncoder _encoder; | ||||
@@ -18,12 +21,14 @@ | |||||
_encoder.SetBitrate(bitrate.Value); | _encoder.SetBitrate(bitrate.Value); | ||||
} | } | ||||
/// <summary> Writes Opus-encoded PCM data to the stream. </summary> | |||||
public override void Write(byte[] buffer, int offset, int count) | public override void Write(byte[] buffer, int offset, int count) | ||||
{ | { | ||||
count = _encoder.EncodeFrame(buffer, offset, count, _buffer, 0); | count = _encoder.EncodeFrame(buffer, offset, count, _buffer, 0); | ||||
base.Write(_buffer, 0, count); | base.Write(_buffer, 0, count); | ||||
} | } | ||||
/// <inheritdoc/> | |||||
protected override void Dispose(bool disposing) | protected override void Dispose(bool disposing) | ||||
{ | { | ||||
base.Dispose(disposing); | base.Dispose(disposing); | ||||
@@ -4,14 +4,18 @@ using System.IO; | |||||
namespace Discord.Audio | namespace Discord.Audio | ||||
{ | { | ||||
/// <summary> A stream used for reading raw audio data from Discord. </summary> | |||||
public class RTPReadStream : Stream | public class RTPReadStream : Stream | ||||
{ | { | ||||
private readonly BlockingCollection<byte[]> _queuedData; //TODO: Replace with max-length ring buffer | private readonly BlockingCollection<byte[]> _queuedData; //TODO: Replace with max-length ring buffer | ||||
private readonly AudioClient _audioClient; | private readonly AudioClient _audioClient; | ||||
private readonly byte[] _buffer, _nonce, _secretKey; | private readonly byte[] _buffer, _nonce, _secretKey; | ||||
/// <inheritdoc/> | |||||
public override bool CanRead => true; | public override bool CanRead => true; | ||||
/// <inheritdoc/> | |||||
public override bool CanSeek => false; | public override bool CanSeek => false; | ||||
/// <inheritdoc/> | |||||
public override bool CanWrite => true; | public override bool CanWrite => true; | ||||
internal RTPReadStream(AudioClient audioClient, byte[] secretKey, int bufferSize = 4000) | internal RTPReadStream(AudioClient audioClient, byte[] secretKey, int bufferSize = 4000) | ||||
@@ -23,12 +27,14 @@ namespace Discord.Audio | |||||
_nonce = new byte[24]; | _nonce = new byte[24]; | ||||
} | } | ||||
/// <inheritdoc/> | |||||
public override int Read(byte[] buffer, int offset, int count) | public override int Read(byte[] buffer, int offset, int count) | ||||
{ | { | ||||
var queuedData = _queuedData.Take(); | var queuedData = _queuedData.Take(); | ||||
Buffer.BlockCopy(queuedData, 0, buffer, offset, Math.Min(queuedData.Length, count)); | Buffer.BlockCopy(queuedData, 0, buffer, offset, Math.Min(queuedData.Length, count)); | ||||
return queuedData.Length; | return queuedData.Length; | ||||
} | } | ||||
/// <inheritdoc/> | |||||
public override void Write(byte[] buffer, int offset, int count) | public override void Write(byte[] buffer, int offset, int count) | ||||
{ | { | ||||
Buffer.BlockCopy(buffer, 0, _nonce, 0, 12); | Buffer.BlockCopy(buffer, 0, _nonce, 0, 12); | ||||
@@ -38,16 +44,21 @@ namespace Discord.Audio | |||||
_queuedData.Add(newBuffer); | _queuedData.Add(newBuffer); | ||||
} | } | ||||
/// <inheritdoc/> | |||||
public override void Flush() { throw new NotSupportedException(); } | public override void Flush() { throw new NotSupportedException(); } | ||||
/// <inheritdoc/> | |||||
public override long Length { get { throw new NotSupportedException(); } } | public override long Length { get { throw new NotSupportedException(); } } | ||||
/// <inheritdoc/> | |||||
public override long Position | public override long Position | ||||
{ | { | ||||
get { throw new NotSupportedException(); } | get { throw new NotSupportedException(); } | ||||
set { throw new NotSupportedException(); } | set { throw new NotSupportedException(); } | ||||
} | } | ||||
/// <inheritdoc/> | |||||
public override void SetLength(long value) { throw new NotSupportedException(); } | public override void SetLength(long value) { throw new NotSupportedException(); } | ||||
/// <inheritdoc/> | |||||
public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } | public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } | ||||
} | } | ||||
} | } |
@@ -3,6 +3,7 @@ using System.IO; | |||||
namespace Discord.Audio | namespace Discord.Audio | ||||
{ | { | ||||
/// <summary> A stream used for writing raw audio data to Discord. </summary> | |||||
public class RTPWriteStream : Stream | public class RTPWriteStream : Stream | ||||
{ | { | ||||
private readonly AudioClient _audioClient; | private readonly AudioClient _audioClient; | ||||
@@ -10,10 +11,14 @@ namespace Discord.Audio | |||||
private int _samplesPerFrame; | private int _samplesPerFrame; | ||||
private uint _ssrc, _timestamp = 0; | private uint _ssrc, _timestamp = 0; | ||||
/// <summary> The current output buffer. </summary> | |||||
protected readonly byte[] _buffer; | protected readonly byte[] _buffer; | ||||
/// <inheritdoc/> | |||||
public override bool CanRead => false; | public override bool CanRead => false; | ||||
/// <inheritdoc/> | |||||
public override bool CanSeek => false; | public override bool CanSeek => false; | ||||
/// <inheritdoc/> | |||||
public override bool CanWrite => true; | public override bool CanWrite => true; | ||||
internal RTPWriteStream(AudioClient audioClient, byte[] secretKey, int samplesPerFrame, uint ssrc, int bufferSize = 4000) | internal RTPWriteStream(AudioClient audioClient, byte[] secretKey, int samplesPerFrame, uint ssrc, int bufferSize = 4000) | ||||
@@ -32,6 +37,7 @@ namespace Discord.Audio | |||||
_nonce[11] = (byte)(_ssrc >> 0); | _nonce[11] = (byte)(_ssrc >> 0); | ||||
} | } | ||||
/// <inheritdoc/> | |||||
public override void Write(byte[] buffer, int offset, int count) | public override void Write(byte[] buffer, int offset, int count) | ||||
{ | { | ||||
unchecked | unchecked | ||||
@@ -51,17 +57,23 @@ namespace Discord.Audio | |||||
_audioClient.Send(_buffer, count + 12); | _audioClient.Send(_buffer, count + 12); | ||||
} | } | ||||
/// <inheritdoc/> | |||||
public override void Flush() { } | public override void Flush() { } | ||||
/// <inheritdoc/> | |||||
public override long Length { get { throw new NotSupportedException(); } } | public override long Length { get { throw new NotSupportedException(); } } | ||||
/// <inheritdoc/> | |||||
public override long Position | public override long Position | ||||
{ | { | ||||
get { throw new NotSupportedException(); } | get { throw new NotSupportedException(); } | ||||
set { throw new NotSupportedException(); } | set { throw new NotSupportedException(); } | ||||
} | } | ||||
/// <inheritdoc/> | |||||
public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } | public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } | ||||
/// <inheritdoc/> | |||||
public override void SetLength(long value) { throw new NotSupportedException(); } | public override void SetLength(long value) { throw new NotSupportedException(); } | ||||
/// <inheritdoc/> | |||||
public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } | public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } | ||||
} | } | ||||
} | } |
@@ -1,10 +1,15 @@ | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
/// <summary> Connection state for clients </summary> | |||||
public enum ConnectionState : byte | public enum ConnectionState : byte | ||||
{ | { | ||||
/// <summary> Not connected to Discord </summary> | |||||
Disconnected, | Disconnected, | ||||
/// <summary> Currently connecting to Discord </summary> | |||||
Connecting, | Connecting, | ||||
/// <summary> Connected to Discord </summary> | |||||
Connected, | Connected, | ||||
/// <summary> Disconnecting from Discord </summary> | |||||
Disconnecting | Disconnecting | ||||
} | } | ||||
} | } |
@@ -2,20 +2,29 @@ | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
/// <summary> Stores common configuration settings </summary> | |||||
public class DiscordConfig | public class DiscordConfig | ||||
{ | { | ||||
public const int APIVersion = 6; | |||||
/// <summary> The version of Discord's REST API which is used </summary> | |||||
public const int APIVersion = 6; | |||||
/// <summary> Version information about Discord.Net </summary> | |||||
public static string Version { get; } = | public static string Version { get; } = | ||||
typeof(DiscordConfig).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? | typeof(DiscordConfig).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? | ||||
typeof(DiscordConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3) ?? | typeof(DiscordConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3) ?? | ||||
"Unknown"; | "Unknown"; | ||||
/// <summary> The base URL for all REST API requests </summary> | |||||
public static readonly string ClientAPIUrl = $"https://discordapp.com/api/v{APIVersion}/"; | public static readonly string ClientAPIUrl = $"https://discordapp.com/api/v{APIVersion}/"; | ||||
/// <summary> The base URL for all CDN requests </summary> | |||||
public const string CDNUrl = "https://discordcdn.com/"; | public const string CDNUrl = "https://discordcdn.com/"; | ||||
/// <summary> The base URL for all invite links </summary> | |||||
public const string InviteUrl = "https://discord.gg/"; | public const string InviteUrl = "https://discord.gg/"; | ||||
/// <summary> The maximum amount of characters which can be sent in a message </summary> | |||||
public const int MaxMessageSize = 2000; | public const int MaxMessageSize = 2000; | ||||
/// <summary> The maximum number of messages which can be received in a batch </summary> | |||||
public const int MaxMessagesPerBatch = 100; | public const int MaxMessagesPerBatch = 100; | ||||
/// <summary> The maximum number of users which can be received in a batch </summary> | |||||
public const int MaxUsersPerBatch = 1000; | public const int MaxUsersPerBatch = 1000; | ||||
/// <summary> Gets or sets the minimum log level severity that will be sent to the LogMessage event. </summary> | /// <summary> Gets or sets the minimum log level severity that will be sent to the LogMessage event. </summary> | ||||
@@ -1,10 +1,15 @@ | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
/// <summary> Specifies the type of channel a message was sent to or eceived from. </summary> | |||||
public enum ChannelType | public enum ChannelType | ||||
{ | { | ||||
///<summary> A text channel </summary> | |||||
Text = 0, | Text = 0, | ||||
///<summary> A direct-message text channel </summary> | |||||
DM = 1, | DM = 1, | ||||
///<summary> A voice channel </summary> | |||||
Voice = 2, | Voice = 2, | ||||
///<summary> A group channel </summary> | |||||
Group = 3 | Group = 3 | ||||
} | } | ||||
} | } |
@@ -1,5 +1,6 @@ | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
/// <summary> Contains common macros for formatting text using Markdown </summary> | |||||
public static class Format | public static class Format | ||||
{ | { | ||||
// Characters which need escaping | // Characters which need escaping | ||||
@@ -1,12 +1,19 @@ | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
/// <summary> The severity of a log message </summary> | |||||
public enum LogSeverity | public enum LogSeverity | ||||
{ | { | ||||
/// <summary> Used when a critical, non-recoverable error occurs </summary> | |||||
Critical = 0, | Critical = 0, | ||||
/// <summary> Used when a recoverable error occurs </summary> | |||||
Error = 1, | Error = 1, | ||||
/// <summary> Used when a warning occurs </summary> | |||||
Warning = 2, | Warning = 2, | ||||
/// <summary> Used for general, informative messages </summary> | |||||
Info = 3, | Info = 3, | ||||
/// <summary> Used for debugging purposes </summary> | |||||
Verbose = 4, | Verbose = 4, | ||||
/// <summary> Used for debugging purposes </summary> | |||||
Debug = 5 | Debug = 5 | ||||
} | } | ||||
} | } |
@@ -1,10 +1,15 @@ | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
/// <summary> Login state for clients </summary> | |||||
public enum LoginState : byte | public enum LoginState : byte | ||||
{ | { | ||||
/// <summary> Logged out </summary> | |||||
LoggedOut, | LoggedOut, | ||||
/// <summary> Logging in </summary> | |||||
LoggingIn, | LoggingIn, | ||||
/// <summary> Logged in </summary> | |||||
LoggedIn, | LoggedIn, | ||||
/// <summary> Logging out </summary> | |||||
LoggingOut | LoggingOut | ||||
} | } | ||||
} | } |
@@ -13,6 +13,7 @@ using System.Threading.Tasks; | |||||
namespace Discord.Net.Rest | namespace Discord.Net.Rest | ||||
{ | { | ||||
///<summary> A default implementation of a <see cref="IRestClient"/> </summary> | |||||
public sealed class DefaultRestClient : IRestClient | public sealed class DefaultRestClient : IRestClient | ||||
{ | { | ||||
private const int HR_SECURECHANNELFAILED = -2146233079; | private const int HR_SECURECHANNELFAILED = -2146233079; | ||||
@@ -24,6 +25,7 @@ namespace Discord.Net.Rest | |||||
private CancellationToken _cancelToken, _parentToken; | private CancellationToken _cancelToken, _parentToken; | ||||
private bool _isDisposed; | private bool _isDisposed; | ||||
/// <summary> Creates a new instance of <see cref="DefaultRestClient"/> </summary> | |||||
public DefaultRestClient(string baseUrl) | public DefaultRestClient(string baseUrl) | ||||
{ | { | ||||
_baseUrl = baseUrl; | _baseUrl = baseUrl; | ||||
@@ -50,29 +52,34 @@ namespace Discord.Net.Rest | |||||
_isDisposed = true; | _isDisposed = true; | ||||
} | } | ||||
} | } | ||||
/// <summary> Disposes any resources allocated by this instance. </summary> | |||||
public void Dispose() | public void Dispose() | ||||
{ | { | ||||
Dispose(true); | Dispose(true); | ||||
} | } | ||||
/// <inheritdoc/> | |||||
public void SetHeader(string key, string value) | public void SetHeader(string key, string value) | ||||
{ | { | ||||
_client.DefaultRequestHeaders.Remove(key); | _client.DefaultRequestHeaders.Remove(key); | ||||
if (value != null) | if (value != null) | ||||
_client.DefaultRequestHeaders.Add(key, value); | _client.DefaultRequestHeaders.Add(key, value); | ||||
} | } | ||||
/// <inheritdoc/> | |||||
public void SetCancelToken(CancellationToken cancelToken) | public void SetCancelToken(CancellationToken cancelToken) | ||||
{ | { | ||||
_parentToken = cancelToken; | _parentToken = cancelToken; | ||||
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; | _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; | ||||
} | } | ||||
/// <inheritdoc/> | |||||
public async Task<Stream> SendAsync(string method, string endpoint, bool headerOnly = false) | public async Task<Stream> SendAsync(string method, string endpoint, bool headerOnly = false) | ||||
{ | { | ||||
string uri = Path.Combine(_baseUrl, endpoint); | string uri = Path.Combine(_baseUrl, endpoint); | ||||
using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) | using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) | ||||
return await SendInternalAsync(restRequest, headerOnly).ConfigureAwait(false); | return await SendInternalAsync(restRequest, headerOnly).ConfigureAwait(false); | ||||
} | } | ||||
/// <inheritdoc/> | |||||
public async Task<Stream> SendAsync(string method, string endpoint, string json, bool headerOnly = false) | public async Task<Stream> SendAsync(string method, string endpoint, string json, bool headerOnly = false) | ||||
{ | { | ||||
string uri = Path.Combine(_baseUrl, endpoint); | string uri = Path.Combine(_baseUrl, endpoint); | ||||
@@ -82,6 +89,7 @@ namespace Discord.Net.Rest | |||||
return await SendInternalAsync(restRequest, headerOnly).ConfigureAwait(false); | return await SendInternalAsync(restRequest, headerOnly).ConfigureAwait(false); | ||||
} | } | ||||
} | } | ||||
/// <inheritdoc/> | |||||
public async Task<Stream> SendAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartParams, bool headerOnly = false) | public async Task<Stream> SendAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartParams, bool headerOnly = false) | ||||
{ | { | ||||
string uri = Path.Combine(_baseUrl, endpoint); | string uri = Path.Combine(_baseUrl, endpoint); | ||||
@@ -8,11 +8,16 @@ namespace Discord.Net.Rest | |||||
//TODO: Add docstrings | //TODO: Add docstrings | ||||
public interface IRestClient | public interface IRestClient | ||||
{ | { | ||||
/// <summary> Sets a header to be used in REST requests. </summary> | |||||
void SetHeader(string key, string value); | void SetHeader(string key, string value); | ||||
/// <summary> Sets the global cancellation token for any requests made by this instance. </summary> | |||||
void SetCancelToken(CancellationToken cancelToken); | void SetCancelToken(CancellationToken cancelToken); | ||||
/// <summary> Sends a request with no body to the given endpoint. </summary> | |||||
Task<Stream> SendAsync(string method, string endpoint, bool headerOnly = false); | Task<Stream> SendAsync(string method, string endpoint, bool headerOnly = false); | ||||
/// <summary> Sends a request with a body to the given endpoint. </summary> | |||||
Task<Stream> SendAsync(string method, string endpoint, string json, bool headerOnly = false); | Task<Stream> SendAsync(string method, string endpoint, string json, bool headerOnly = false); | ||||
/// <summary> Sends a multipart request with the given parameters to the given endpoint. </summary> | |||||
Task<Stream> SendAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartParams, bool headerOnly = false); | Task<Stream> SendAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartParams, bool headerOnly = false); | ||||
} | } | ||||
} | } |
@@ -1,4 +1,5 @@ | |||||
namespace Discord.Net.Rest | namespace Discord.Net.Rest | ||||
{ | { | ||||
/// <summary> A delegate for creating a user-defined implementation of <see cref="IRestClient"/> </summary> | |||||
public delegate IRestClient RestClientProvider(string baseUrl); | public delegate IRestClient RestClientProvider(string baseUrl); | ||||
} | } |
@@ -2,11 +2,15 @@ | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
/// <summary> An exception thrown whenever an RPC error occurs. </summary> | |||||
public class RpcException : Exception | public class RpcException : Exception | ||||
{ | { | ||||
/// <summary> The code for this error. </summary> | |||||
public int ErrorCode { get; } | public int ErrorCode { get; } | ||||
/// <summary> The reason this error occured. </summary> | |||||
public string Reason { get; } | public string Reason { get; } | ||||
/// <summary> Creates a new instance of <see cref="RpcException"/> </summary> | |||||
public RpcException(int errorCode, string reason = null) | public RpcException(int errorCode, string reason = null) | ||||
: base($"The server sent error {errorCode}{(reason != null ? $": \"{reason}\"" : "")}") | : base($"The server sent error {errorCode}{(reason != null ? $": \"{reason}\"" : "")}") | ||||
{ | { | ||||
@@ -1,12 +1,15 @@ | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
/// <summary> Contains options specific to requests </summary> | |||||
public class RequestOptions | public class RequestOptions | ||||
{ | { | ||||
/// <summary> Returns the default options for a request. </summary> | |||||
public static RequestOptions Default => new RequestOptions(); | public static RequestOptions Default => new RequestOptions(); | ||||
/// <summary> The max time, in milliseconds, to wait for this request to complete. If null, a request will not time out. If a rate limit has been triggered for this request's bucket and will not be unpaused in time, this request will fail immediately. </summary> | /// <summary> The max time, in milliseconds, to wait for this request to complete. If null, a request will not time out. If a rate limit has been triggered for this request's bucket and will not be unpaused in time, this request will fail immediately. </summary> | ||||
public int? Timeout { get; set; } | public int? Timeout { get; set; } | ||||
/// <summary> Creates a new instance of the RequestOptions class </summary> | |||||
public RequestOptions() | public RequestOptions() | ||||
{ | { | ||||
Timeout = 30000; | Timeout = 30000; | ||||
@@ -15,15 +15,19 @@ using Discord.WebSocket; | |||||
namespace Discord.Rest | namespace Discord.Rest | ||||
{ | { | ||||
/// <summary> A client which invokes Discord's REST API. </summary> | |||||
public class DiscordRestClient : IDiscordClient | public class DiscordRestClient : IDiscordClient | ||||
{ | { | ||||
private readonly object _eventLock = new object(); | private readonly object _eventLock = new object(); | ||||
/// <summary> Fired whenever a message is logged. </summary> | |||||
public event Func<LogMessage, Task> Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } | public event Func<LogMessage, Task> Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } | ||||
private readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>(); | private readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>(); | ||||
/// <summary> Fired whenever the client logs in. </summary> | |||||
public event Func<Task> LoggedIn { add { _loggedInEvent.Add(value); } remove { _loggedInEvent.Remove(value); } } | public event Func<Task> LoggedIn { add { _loggedInEvent.Add(value); } remove { _loggedInEvent.Remove(value); } } | ||||
private readonly AsyncEvent<Func<Task>> _loggedInEvent = new AsyncEvent<Func<Task>>(); | private readonly AsyncEvent<Func<Task>> _loggedInEvent = new AsyncEvent<Func<Task>>(); | ||||
/// <summary> Fired whenever the client logs out. </summary> | |||||
public event Func<Task> LoggedOut { add { _loggedOutEvent.Add(value); } remove { _loggedOutEvent.Remove(value); } } | public event Func<Task> LoggedOut { add { _loggedOutEvent.Add(value); } remove { _loggedOutEvent.Remove(value); } } | ||||
private readonly AsyncEvent<Func<Task>> _loggedOutEvent = new AsyncEvent<Func<Task>>(); | private readonly AsyncEvent<Func<Task>> _loggedOutEvent = new AsyncEvent<Func<Task>>(); | ||||
@@ -33,12 +37,15 @@ namespace Discord.Rest | |||||
private bool _isFirstLogSub; | private bool _isFirstLogSub; | ||||
internal bool _isDisposed; | internal bool _isDisposed; | ||||
/// <summary> The API client used for making API calls. </summary> | |||||
public API.DiscordRestApiClient ApiClient { get; } | public API.DiscordRestApiClient ApiClient { get; } | ||||
internal LogManager LogManager { get; } | internal LogManager LogManager { get; } | ||||
/// <summary> The current login state of the client. </summary> | |||||
public LoginState LoginState { get; private set; } | public LoginState LoginState { get; private set; } | ||||
/// <summary> Creates a new REST-only discord client. </summary> | /// <summary> Creates a new REST-only discord client. </summary> | ||||
public DiscordRestClient() : this(new DiscordRestConfig()) { } | public DiscordRestClient() : this(new DiscordRestConfig()) { } | ||||
/// <summary> Creates a new REST-only discord client. </summary> | |||||
public DiscordRestClient(DiscordRestConfig config) : this(config, CreateApiClient(config)) { } | public DiscordRestClient(DiscordRestConfig config) : this(config, CreateApiClient(config)) { } | ||||
/// <summary> Creates a new REST-only discord client. </summary> | /// <summary> Creates a new REST-only discord client. </summary> | ||||
internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient client) | internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient client) | ||||
@@ -103,6 +110,7 @@ namespace Discord.Rest | |||||
await _loggedInEvent.InvokeAsync().ConfigureAwait(false); | await _loggedInEvent.InvokeAsync().ConfigureAwait(false); | ||||
} | } | ||||
/// <summary> Validates a token with the given type. </summary> | |||||
protected virtual async Task ValidateTokenAsync(TokenType tokenType, string token) | protected virtual async Task ValidateTokenAsync(TokenType tokenType, string token) | ||||
{ | { | ||||
try | try | ||||
@@ -121,6 +129,7 @@ namespace Discord.Rest | |||||
throw new ArgumentException("Token validation failed", nameof(token), ex); | throw new ArgumentException("Token validation failed", nameof(token), ex); | ||||
} | } | ||||
} | } | ||||
/// <summary> A Promise for when the client successfully logs in. </summary> | |||||
protected virtual Task OnLoginAsync(TokenType tokenType, string token) => Task.CompletedTask; | protected virtual Task OnLoginAsync(TokenType tokenType, string token) => Task.CompletedTask; | ||||
@@ -149,6 +158,7 @@ namespace Discord.Rest | |||||
await _loggedOutEvent.InvokeAsync().ConfigureAwait(false); | await _loggedOutEvent.InvokeAsync().ConfigureAwait(false); | ||||
} | } | ||||
/// <summary> A Promise for when the client successfully logs out. </summary> | |||||
protected virtual Task OnLogoutAsync() => Task.CompletedTask; | protected virtual Task OnLogoutAsync() => Task.CompletedTask; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
@@ -2,8 +2,10 @@ | |||||
namespace Discord.Rest | namespace Discord.Rest | ||||
{ | { | ||||
/// <summary> A set of common configuration options for REST clients. </summary> | |||||
public class DiscordRestConfig : DiscordConfig | public class DiscordRestConfig : DiscordConfig | ||||
{ | { | ||||
/// <summary> Gets the user agent used in REST API calls </summary> | |||||
public static string UserAgent { get; } = $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})"; | public static string UserAgent { get; } = $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})"; | ||||
internal const int RestTimeout = 10000; | internal const int RestTimeout = 10000; | ||||