@@ -47,6 +47,9 @@ | |||||
<Compile Include="..\Discord.Net.Audio\AudioExtensions.cs"> | <Compile Include="..\Discord.Net.Audio\AudioExtensions.cs"> | ||||
<Link>AudioExtensions.cs</Link> | <Link>AudioExtensions.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net.Audio\AudioMode.cs"> | |||||
<Link>AudioMode.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Audio\AudioService.cs"> | <Compile Include="..\Discord.Net.Audio\AudioService.cs"> | ||||
<Link>AudioService.cs</Link> | <Link>AudioService.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -61,8 +61,8 @@ namespace Discord.Audio | |||||
public Stream OutputStream { get; } | public Stream OutputStream { get; } | ||||
public CancellationToken CancelToken { get; private set; } | public CancellationToken CancelToken { get; private set; } | ||||
public string SessionId { get; private set; } | |||||
public string SessionId => GatewaySocket.SessionId; | |||||
public ConnectionState State => VoiceSocket.State; | public ConnectionState State => VoiceSocket.State; | ||||
public Server Server => VoiceSocket.Server; | public Server Server => VoiceSocket.Server; | ||||
public Channel Channel => VoiceSocket.Channel; | public Channel Channel => VoiceSocket.Channel; | ||||
@@ -71,7 +71,7 @@ namespace Discord.Audio | |||||
{ | { | ||||
Id = id; | Id = id; | ||||
_config = client.Config; | _config = client.Config; | ||||
Service = client.Audio(); | |||||
Service = client.Services.Get<AudioService>(); | |||||
Config = Service.Config; | Config = Service.Config; | ||||
Serializer = client.Serializer; | Serializer = client.Serializer; | ||||
_gatewayState = (int)ConnectionState.Disconnected; | _gatewayState = (int)ConnectionState.Disconnected; | ||||
@@ -1,4 +1,5 @@ | |||||
using System; | using System; | ||||
using System.Threading.Tasks; | |||||
namespace Discord.Audio | namespace Discord.Audio | ||||
{ | { | ||||
@@ -9,14 +10,17 @@ namespace Discord.Audio | |||||
client.Services.Add(new AudioService(config)); | client.Services.Add(new AudioService(config)); | ||||
return client; | return client; | ||||
} | } | ||||
public static DiscordClient UsingAudio(this DiscordClient client, Action<AudioServiceConfig> configFunc = null) | |||||
public static DiscordClient UsingAudio(this DiscordClient client, Action<AudioServiceConfigBuilder> configFunc = null) | |||||
{ | { | ||||
var config = new AudioServiceConfig(); | |||||
configFunc(config); | |||||
client.Services.Add(new AudioService(config)); | |||||
var builder = new AudioServiceConfigBuilder(); | |||||
configFunc(builder); | |||||
client.Services.Add(new AudioService(builder)); | |||||
return client; | return client; | ||||
} | } | ||||
public static AudioService Audio(this DiscordClient client, bool required = true) | |||||
=> client.Services.Get<AudioService>(required); | |||||
public static Task<IAudioClient> JoinAudio(this Channel channel) => channel.Client.Services.Get<AudioService>().Join(channel); | |||||
public static Task LeaveAudio(this Channel channel) => channel.Client.Services.Get<AudioService>().Leave(channel); | |||||
public static Task LeaveAudio(this Server server) => server.Client.Services.Get<AudioService>().Leave(server); | |||||
public static IAudioClient GetAudioClient(Server server) => server.Client.Services.Get<AudioService>().GetClient(server); | |||||
} | } | ||||
} | } |
@@ -0,0 +1,9 @@ | |||||
namespace Discord.Audio | |||||
{ | |||||
public enum AudioMode : byte | |||||
{ | |||||
Outgoing = 1, | |||||
Incoming = 2, | |||||
Both = Outgoing | Incoming | |||||
} | |||||
} |
@@ -29,16 +29,23 @@ namespace Discord.Audio | |||||
private void OnUserIsSpeakingUpdated(User user, bool isSpeaking) | private void OnUserIsSpeakingUpdated(User user, bool isSpeaking) | ||||
=> UserIsSpeakingUpdated(this, new UserIsSpeakingEventArgs(user, isSpeaking)); | => UserIsSpeakingUpdated(this, new UserIsSpeakingEventArgs(user, isSpeaking)); | ||||
public AudioService(AudioServiceConfig config) | |||||
public AudioService() | |||||
: this(new AudioServiceConfigBuilder()) | |||||
{ | |||||
} | |||||
public AudioService(AudioServiceConfigBuilder builder) | |||||
: this(builder.Build()) | |||||
{ | |||||
} | |||||
public AudioService(AudioServiceConfig config) | |||||
{ | { | ||||
Config = config; | |||||
Config = config; | |||||
_asyncLock = new AsyncLock(); | _asyncLock = new AsyncLock(); | ||||
} | } | ||||
void IService.Install(DiscordClient client) | void IService.Install(DiscordClient client) | ||||
{ | { | ||||
Client = client; | Client = client; | ||||
Config.Lock(); | |||||
if (Config.EnableMultiserver) | if (Config.EnableMultiserver) | ||||
_voiceClients = new ConcurrentDictionary<ulong, AudioClient>(); | _voiceClients = new ConcurrentDictionary<ulong, AudioClient>(); | ||||
@@ -1,62 +1,51 @@ | |||||
| |||||
using System; | |||||
namespace Discord.Audio | |||||
namespace Discord.Audio | |||||
{ | { | ||||
public enum AudioMode : byte | |||||
public class AudioServiceConfigBuilder | |||||
{ | { | ||||
Outgoing = 1, | |||||
Incoming = 2, | |||||
Both = Outgoing | Incoming | |||||
} | |||||
/// <summary> Enables the voice websocket and UDP client and specifies how it will be used. </summary> | |||||
public AudioMode Mode { get; set; } = AudioMode.Outgoing; | |||||
public class AudioServiceConfig | |||||
{ | |||||
/// <summary> Enables the voice websocket and UDP client. This option requires the libsodium .dll or .so be in the local or system folder. </summary> | |||||
public bool EnableEncryption { get; set; } = true; | |||||
/// <summary> | |||||
/// Enables the client to be simultaneously connected to multiple channels at once (Discord still limits you to one channel per server). | |||||
/// This option uses a lot of CPU power and network bandwidth, as a new gateway connection needs to be spun up per server. Use sparingly. | |||||
/// </summary> | |||||
public bool EnableMultiserver { get; set; } = false; | |||||
/// <summary> Gets or sets the buffer length (in milliseconds) for outgoing voice packets. </summary> | |||||
public int BufferLength { get; set; } = 1000; | |||||
/// <summary> Gets or sets the bitrate used (in kbit/s, between 1 and MaxBitrate inclusively) for outgoing voice packets. A null value will use default Opus settings. </summary> | |||||
public int? Bitrate { get; set; } = null; | |||||
/// <summary> Gets or sets the number of channels (1 or 2) used in both input provided to IAudioClient and output send to Discord. Defaults to 2 (stereo). </summary> | |||||
public int Channels { get; set; } = 2; | |||||
public AudioServiceConfig Build() => new AudioServiceConfig(this); | |||||
} | |||||
public class AudioServiceConfig | |||||
{ | |||||
public const int MaxBitrate = 128; | public const int MaxBitrate = 128; | ||||
/// <summary> Max time in milliseconds to wait for DiscordAudioClient to connect and initialize. </summary> | |||||
public int ConnectionTimeout { get { return _connectionTimeout; } set { SetValue(ref _connectionTimeout, value); } } | |||||
private int _connectionTimeout = 30000; | |||||
public AudioMode Mode { get; } | |||||
//Experimental Features | |||||
/// <summary> (Experimental) Enables the voice websocket and UDP client and specifies how it will be used. </summary> | |||||
public AudioMode Mode { get { return _mode; } set { SetValue(ref _mode, value); } } | |||||
private AudioMode _mode = AudioMode.Outgoing; | |||||
public bool EnableEncryption { get; } | |||||
public bool EnableMultiserver { get; } | |||||
/// <summary> (Experimental) Enables the voice websocket and UDP client. This option requires the libsodium .dll or .so be in the local or system folder. </summary> | |||||
public bool EnableEncryption { get { return _enableEncryption; } set { SetValue(ref _enableEncryption, value); } } | |||||
private bool _enableEncryption = true; | |||||
public int BufferLength { get; } | |||||
public int? Bitrate { get; } | |||||
public int Channels { get; } | |||||
/// <summary> (Experimental) Enables the client to be simultaneously connected to multiple channels at once (Discord still limits you to one channel per server). </summary> | |||||
public bool EnableMultiserver { get { return _enableMultiserver; } set { SetValue(ref _enableMultiserver, value); } } | |||||
private bool _enableMultiserver = false; | |||||
internal AudioServiceConfig(AudioServiceConfigBuilder builder) | |||||
{ | |||||
Mode = builder.Mode; | |||||
/// <summary> Gets or sets the buffer length (in milliseconds) for outgoing voice packets. </summary> | |||||
public int BufferLength { get { return _bufferLength; } set { SetValue(ref _bufferLength, value); } } | |||||
private int _bufferLength = 1000; | |||||
EnableEncryption = builder.EnableEncryption; | |||||
EnableMultiserver = builder.EnableMultiserver; | |||||
/// <summary> Gets or sets the bitrate used (in kbit/s, between 1 and MaxBitrate inclusively) for outgoing voice packets. A null value will use default Opus settings. </summary> | |||||
public int? Bitrate { get { return _bitrate; } set { SetValue(ref _bitrate, value); } } | |||||
private int? _bitrate = null; | |||||
/// <summary> Gets or sets the number of channels (1 or 2) used in both input provided to IAudioClient and output send to Discord. Defaults to 2 (stereo). </summary> | |||||
public int Channels { get { return _channels; } set { SetValue(ref _channels, value); } } | |||||
private int _channels = 2; | |||||
//Lock | |||||
protected bool _isLocked; | |||||
internal void Lock() { _isLocked = true; } | |||||
protected void SetValue<T>(ref T storage, T value) | |||||
{ | |||||
if (_isLocked) | |||||
throw new InvalidOperationException("Unable to modify a service's configuration after it has been created."); | |||||
storage = value; | |||||
} | |||||
public AudioServiceConfig Clone() | |||||
{ | |||||
var config = MemberwiseClone() as AudioServiceConfig; | |||||
config._isLocked = false; | |||||
return config; | |||||
} | |||||
} | |||||
BufferLength = builder.BufferLength; | |||||
Bitrate = builder.Bitrate; | |||||
Channels = builder.Channels; | |||||
} | |||||
} | |||||
} | } |
@@ -9,14 +9,12 @@ namespace Discord.Commands | |||||
client.Services.Add(new CommandService(config)); | client.Services.Add(new CommandService(config)); | ||||
return client; | return client; | ||||
} | } | ||||
public static DiscordClient UsingCommands(this DiscordClient client, Action<CommandServiceConfig> configFunc = null) | |||||
public static DiscordClient UsingCommands(this DiscordClient client, Action<CommandServiceConfigBuilder> configFunc = null) | |||||
{ | { | ||||
var config = new CommandServiceConfig(); | |||||
configFunc(config); | |||||
client.Services.Add(new CommandService(config)); | |||||
var builder = new CommandServiceConfigBuilder(); | |||||
configFunc(builder); | |||||
client.Services.Add(new CommandService(builder)); | |||||
return client; | return client; | ||||
} | } | ||||
public static CommandService Commands(this DiscordClient client, bool required = true) | |||||
=> client.Services.Get<CommandService>(required); | |||||
} | } | ||||
} | } |
@@ -29,6 +29,18 @@ namespace Discord.Commands | |||||
private void OnCommandError(CommandErrorType errorType, CommandEventArgs args, Exception ex = null) | private void OnCommandError(CommandErrorType errorType, CommandEventArgs args, Exception ex = null) | ||||
=> CommandErrored(this, new CommandErrorEventArgs(errorType, args, ex)); | => CommandErrored(this, new CommandErrorEventArgs(errorType, args, ex)); | ||||
public CommandService() | |||||
: this(new CommandServiceConfigBuilder()) | |||||
{ | |||||
} | |||||
public CommandService(CommandServiceConfigBuilder builder) | |||||
: this(builder.Build()) | |||||
{ | |||||
if (builder.ExecuteHandler != null) | |||||
CommandExecuted += builder.ExecuteHandler; | |||||
if (builder.ErrorHandler != null) | |||||
CommandErrored += builder.ErrorHandler; | |||||
} | |||||
public CommandService(CommandServiceConfig config) | public CommandService(CommandServiceConfig config) | ||||
{ | { | ||||
Config = config; | Config = config; | ||||
@@ -42,9 +54,8 @@ namespace Discord.Commands | |||||
void IService.Install(DiscordClient client) | void IService.Install(DiscordClient client) | ||||
{ | { | ||||
Client = client; | Client = client; | ||||
Config.Lock(); | |||||
if (Config.HelpMode != HelpMode.Disable) | |||||
if (Config.HelpMode != HelpMode.Disabled) | |||||
{ | { | ||||
CreateCommand("help") | CreateCommand("help") | ||||
.Parameter("command", ParameterType.Multiple) | .Parameter("command", ParameterType.Multiple) | ||||
@@ -2,36 +2,45 @@ | |||||
namespace Discord.Commands | namespace Discord.Commands | ||||
{ | { | ||||
public class CommandServiceConfig | |||||
public class CommandServiceConfigBuilder | |||||
{ | { | ||||
/// <summary> Gets or sets the prefix character used to trigger commands, if ActivationMode has the Char flag set. </summary> | /// <summary> Gets or sets the prefix character used to trigger commands, if ActivationMode has the Char flag set. </summary> | ||||
public char? PrefixChar { get { return _prefixChar; } set { SetValue(ref _prefixChar, value); } } | |||||
private char? _prefixChar = null; | |||||
public char? PrefixChar { get; set; } = null; | |||||
/// <summary> Gets or sets whether a message beginning with a mention to the logged-in user should be treated as a command. </summary> | /// <summary> Gets or sets whether a message beginning with a mention to the logged-in user should be treated as a command. </summary> | ||||
public bool AllowMentionPrefix { get { return _allowMentionPrefix; } set { SetValue(ref _allowMentionPrefix, value); } } | |||||
private bool _allowMentionPrefix = true; | |||||
public bool AllowMentionPrefix { get; set; } = true; | |||||
/// <summary> | /// <summary> | ||||
/// Gets or sets a custom function used to detect messages that should be treated as commands. | /// Gets or sets a custom function used to detect messages that should be treated as commands. | ||||
/// This function should a positive one indicating the index of where the in the message's RawText the command begins, | /// This function should a positive one indicating the index of where the in the message's RawText the command begins, | ||||
/// and a negative value if the message should be ignored. | /// and a negative value if the message should be ignored. | ||||
/// </summary> | /// </summary> | ||||
public Func<Message, int> CustomPrefixHandler { get { return _customPrefixHandler; } set { SetValue(ref _customPrefixHandler, value); } } | |||||
private Func<Message, int> _customPrefixHandler = null; | |||||
public Func<Message, int> CustomPrefixHandler { get; set; } = null; | |||||
/// <summary> Gets or sets whether a help function should be automatically generated. </summary> | |||||
public HelpMode HelpMode { get; set; } = HelpMode.Disabled; | |||||
/// <summary> Gets or sets a handler that is called on any successful command execution. </summary> | |||||
public EventHandler<CommandEventArgs> ExecuteHandler { get; set; } | |||||
/// <summary> Gets or sets a handler that is called on any error during command parsing or execution. </summary> | |||||
public EventHandler<CommandErrorEventArgs> ErrorHandler { get; set; } | |||||
public CommandServiceConfig Build() => new CommandServiceConfig(this); | |||||
} | |||||
public class CommandServiceConfig | |||||
{ | |||||
public char? PrefixChar { get; } | |||||
public bool AllowMentionPrefix { get; } | |||||
public Func<Message, int> CustomPrefixHandler { get; } | |||||
/// <summary> Gets or sets whether a help function should be automatically generated. </summary> | /// <summary> Gets or sets whether a help function should be automatically generated. </summary> | ||||
public HelpMode HelpMode { get { return _helpMode; } set { SetValue(ref _helpMode, value); } } | |||||
private HelpMode _helpMode = HelpMode.Disable; | |||||
public HelpMode HelpMode { get; set; } = HelpMode.Disabled; | |||||
//Lock | |||||
protected bool _isLocked; | |||||
internal void Lock() { _isLocked = true; } | |||||
protected void SetValue<T>(ref T storage, T value) | |||||
{ | |||||
if (_isLocked) | |||||
throw new InvalidOperationException("Unable to modify a service's configuration after it has been created."); | |||||
storage = value; | |||||
} | |||||
internal CommandServiceConfig(CommandServiceConfigBuilder builder) | |||||
{ | |||||
PrefixChar = builder.PrefixChar; | |||||
AllowMentionPrefix = builder.AllowMentionPrefix; | |||||
CustomPrefixHandler = builder.CustomPrefixHandler; | |||||
HelpMode = builder.HelpMode; | |||||
} | |||||
} | } | ||||
} | } |
@@ -3,7 +3,7 @@ | |||||
public enum HelpMode | public enum HelpMode | ||||
{ | { | ||||
/// <summary> Disable the automatic help command. </summary> | /// <summary> Disable the automatic help command. </summary> | ||||
Disable, | |||||
Disabled, | |||||
/// <summary> Use the automatic help command and respond in the channel the command is used. </summary> | /// <summary> Use the automatic help command and respond in the channel the command is used. </summary> | ||||
Public, | Public, | ||||
/// <summary> Use the automatic help command and respond in a private message. </summary> | /// <summary> Use the automatic help command and respond in a private message. </summary> | ||||
@@ -113,7 +113,7 @@ namespace Discord.Modules | |||||
public void CreateCommands(string prefix, Action<CommandGroupBuilder> config) | public void CreateCommands(string prefix, Action<CommandGroupBuilder> config) | ||||
{ | { | ||||
var commandService = Client.Commands(true); | |||||
var commandService = Client.Services.Get<CommandService>(); | |||||
commandService.CreateGroup(prefix, x => | commandService.CreateGroup(prefix, x => | ||||
{ | { | ||||
x.Category(Name); | x.Category(Name); | ||||
@@ -409,6 +409,9 @@ | |||||
<Compile Include="..\Discord.Net\Enums\ImageType.cs"> | <Compile Include="..\Discord.Net\Enums\ImageType.cs"> | ||||
<Link>Enums\ImageType.cs</Link> | <Link>Enums\ImageType.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Enums\LogSeverity.cs"> | |||||
<Link>Enums\LogSeverity.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Enums\PermissionTarget.cs"> | <Compile Include="..\Discord.Net\Enums\PermissionTarget.cs"> | ||||
<Link>Enums\PermissionTarget.cs</Link> | <Link>Enums\PermissionTarget.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -5,15 +5,11 @@ namespace Discord | |||||
{ | { | ||||
internal static class TaskHelper | internal static class TaskHelper | ||||
{ | { | ||||
public static Task CompletedTask { get; } | |||||
static TaskHelper() | |||||
{ | |||||
#if DOTNET54 | #if DOTNET54 | ||||
CompletedTask = Task.CompletedTask; | |||||
public static Task CompletedTask => Task.CompletedTask; | |||||
#else | #else | ||||
CompletedTask = Task.Delay(0); | |||||
public static Task CompletedTask => Task.Delay(0); | |||||
#endif | #endif | ||||
} | |||||
public static Func<Task> ToAsync(Action action) | public static Func<Task> ToAsync(Action action) | ||||
{ | { | ||||
@@ -1,24 +0,0 @@ | |||||
using System; | |||||
namespace Discord | |||||
{ | |||||
public abstract class Config<T> | |||||
where T : Config<T> | |||||
{ | |||||
protected bool _isLocked; | |||||
protected internal void Lock() { _isLocked = true; } | |||||
protected void SetValue<U>(ref U storage, U value) | |||||
{ | |||||
if (_isLocked) | |||||
throw new InvalidOperationException("Unable to modify a discord client's configuration after it has been created."); | |||||
storage = value; | |||||
} | |||||
public T Clone() | |||||
{ | |||||
var config = MemberwiseClone() as T; | |||||
config._isLocked = false; | |||||
return config; | |||||
} | |||||
} | |||||
} |
@@ -76,28 +76,33 @@ namespace Discord | |||||
public IEnumerable<Region> Regions => _regions.Select(x => x.Value); | public IEnumerable<Region> Regions => _regions.Select(x => x.Value); | ||||
/// <summary> Initializes a new instance of the DiscordClient class. </summary> | /// <summary> Initializes a new instance of the DiscordClient class. </summary> | ||||
public DiscordClient(Action<DiscordConfig> configFunc) | |||||
public DiscordClient(Action<DiscordConfigBuilder> configFunc) | |||||
: this(ProcessConfig(configFunc)) | : this(ProcessConfig(configFunc)) | ||||
{ | { | ||||
} | } | ||||
private static DiscordConfig ProcessConfig(Action<DiscordConfig> func) | |||||
private static DiscordConfigBuilder ProcessConfig(Action<DiscordConfigBuilder> func) | |||||
{ | { | ||||
var config = new DiscordConfig(); | |||||
var config = new DiscordConfigBuilder(); | |||||
func(config); | func(config); | ||||
return config; | return config; | ||||
} | } | ||||
/// <summary> Initializes a new instance of the DiscordClient class. </summary> | /// <summary> Initializes a new instance of the DiscordClient class. </summary> | ||||
public DiscordClient() | public DiscordClient() | ||||
: this((DiscordConfig)null) | |||||
: this(new DiscordConfigBuilder()) | |||||
{ | { | ||||
} | } | ||||
/// <summary> Initializes a new instance of the DiscordClient class. </summary> | |||||
public DiscordClient(DiscordConfigBuilder builder) | |||||
: this(builder.Build()) | |||||
{ | |||||
if (builder.LogHandler != null) | |||||
Log.Message += builder.LogHandler; | |||||
} | |||||
/// <summary> Initializes a new instance of the DiscordClient class. </summary> | /// <summary> Initializes a new instance of the DiscordClient class. </summary> | ||||
public DiscordClient(DiscordConfig config) | public DiscordClient(DiscordConfig config) | ||||
{ | { | ||||
Config = config ?? new DiscordConfig(); | |||||
Config.Lock(); | |||||
Config = config; | |||||
State = (int)ConnectionState.Disconnected; | State = (int)ConnectionState.Disconnected; | ||||
Status = UserStatus.Online; | Status = UserStatus.Online; | ||||
@@ -145,9 +150,8 @@ namespace Discord | |||||
}; | }; | ||||
//GatewaySocket.Disconnected += (s, e) => OnDisconnected(e.WasUnexpected, e.Exception); | //GatewaySocket.Disconnected += (s, e) => OnDisconnected(e.WasUnexpected, e.Exception); | ||||
GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e); | GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e); | ||||
if (Config.UseMessageQueue) | |||||
MessageQueue = new MessageQueue(ClientAPI, Log.CreateLogger("MessageQueue")); | |||||
MessageQueue = new MessageQueue(ClientAPI, Log.CreateLogger("MessageQueue")); | |||||
//Extensibility | //Extensibility | ||||
Services = new ServiceManager(this); | Services = new ServiceManager(this); | ||||
@@ -194,10 +198,11 @@ namespace Discord | |||||
await Login(email, password, token).ConfigureAwait(false); | await Login(email, password, token).ConfigureAwait(false); | ||||
await GatewaySocket.Connect(ClientAPI, CancelToken).ConfigureAwait(false); | await GatewaySocket.Connect(ClientAPI, CancelToken).ConfigureAwait(false); | ||||
List<Task> tasks = new List<Task>(); | |||||
tasks.Add(CancelToken.Wait()); | |||||
if (Config.UseMessageQueue) | |||||
tasks.Add(MessageQueue.Run(CancelToken, Config.MessageQueueInterval)); | |||||
Task[] tasks = new[] | |||||
{ | |||||
CancelToken.Wait(), | |||||
MessageQueue.Run(CancelToken) | |||||
}; | |||||
await _taskManager.Start(tasks, cancelSource).ConfigureAwait(false); | await _taskManager.Start(tasks, cancelSource).ConfigureAwait(false); | ||||
GatewaySocket.WaitForConnection(CancelToken); | GatewaySocket.WaitForConnection(CancelToken); | ||||
@@ -222,7 +227,7 @@ namespace Discord | |||||
byte[] cacheKey = null; | byte[] cacheKey = null; | ||||
//Get Token | //Get Token | ||||
if (email != null && Config.CacheToken) | |||||
if (email != null && Config.CacheDir != null) | |||||
{ | { | ||||
tokenPath = GetTokenCachePath(email); | tokenPath = GetTokenCachePath(email); | ||||
if (token == null && password != null) | if (token == null && password != null) | ||||
@@ -240,7 +245,7 @@ namespace Discord | |||||
var request = new LoginRequest() { Email = email, Password = password }; | var request = new LoginRequest() { Email = email, Password = password }; | ||||
var response = await ClientAPI.Send(request).ConfigureAwait(false); | var response = await ClientAPI.Send(request).ConfigureAwait(false); | ||||
token = response.Token; | token = response.Token; | ||||
if (Config.CacheToken && token != oldToken && tokenPath != null) | |||||
if (Config.CacheDir != null && token != oldToken && tokenPath != null) | |||||
SaveToken(tokenPath, cacheKey, token); | SaveToken(tokenPath, cacheKey, token); | ||||
ClientAPI.Token = token; | ClientAPI.Token = token; | ||||
@@ -270,9 +275,8 @@ namespace Discord | |||||
try { await ClientAPI.Send(new LogoutRequest()).ConfigureAwait(false); } | try { await ClientAPI.Send(new LogoutRequest()).ConfigureAwait(false); } | ||||
catch (OperationCanceledException) { } | catch (OperationCanceledException) { } | ||||
} | } | ||||
if (Config.UseMessageQueue) | |||||
MessageQueue.Clear(); | |||||
MessageQueue.Clear(); | |||||
await GatewaySocket.Disconnect().ConfigureAwait(false); | await GatewaySocket.Disconnect().ConfigureAwait(false); | ||||
ClientAPI.Token = null; | ClientAPI.Token = null; | ||||
@@ -1053,7 +1057,7 @@ namespace Discord | |||||
StringBuilder filenameBuilder = new StringBuilder(); | StringBuilder filenameBuilder = new StringBuilder(); | ||||
for (int i = 0; i < data.Length; i++) | for (int i = 0; i < data.Length; i++) | ||||
filenameBuilder.Append(data[i].ToString("x2")); | filenameBuilder.Append(data[i].ToString("x2")); | ||||
return Path.Combine(Path.GetTempPath(), Config.AppName ?? "Discord.Net", filenameBuilder.ToString()); | |||||
return Path.Combine(Config.CacheDir, filenameBuilder.ToString()); | |||||
} | } | ||||
} | } | ||||
private string LoadToken(string path, byte[] key) | private string LoadToken(string path, byte[] key) | ||||
@@ -1,127 +1,133 @@ | |||||
using Newtonsoft.Json; | |||||
using System; | |||||
using System; | |||||
using System.IO; | |||||
using System.Reflection; | using System.Reflection; | ||||
using System.Text; | using System.Text; | ||||
namespace Discord | namespace Discord | ||||
{ | |||||
public enum LogSeverity : byte | |||||
{ | |||||
Error = 1, | |||||
Warning = 2, | |||||
Info = 3, | |||||
Verbose = 4, | |||||
Debug = 5 | |||||
} | |||||
public class DiscordConfig : Config<DiscordConfig> | |||||
{ | |||||
public class DiscordConfigBuilder | |||||
{ | { | ||||
public const int MaxMessageSize = 2000; | |||||
public const string LibName = "Discord.Net"; | |||||
public static string LibVersion => typeof(DiscordConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3); | |||||
public const string LibUrl = "https://github.com/RogueException/Discord.Net"; | |||||
public const string ClientAPIUrl = "https://discordapp.com/api/"; | |||||
public const string StatusAPIUrl = "https://srhpyqt94yxb.statuspage.io/api/v2/"; //"https://status.discordapp.com/api/v2/"; | |||||
//public const string CDNUrl = "https://cdn.discordapp.com/"; | |||||
public const string InviteUrl = "https://discord.gg/"; | |||||
//Global | //Global | ||||
/// <summary> Gets or sets name of your application, used both for the token cache directory and user agent. </summary> | /// <summary> Gets or sets name of your application, used both for the token cache directory and user agent. </summary> | ||||
public string AppName { get { return _appName; } set { SetValue(ref _appName, value); UpdateUserAgent(); } } | |||||
private string _appName = null; | |||||
public string AppName { get; set; } = null; | |||||
/// <summary> Gets or sets url to your application, used in the user agent. </summary> | /// <summary> Gets or sets url to your application, used in the user agent. </summary> | ||||
public string AppUrl { get { return _appUrl; } set { SetValue(ref _appUrl, value); UpdateUserAgent(); } } | |||||
private string _appUrl = null; | |||||
public string AppUrl { get; set; } = null; | |||||
/// <summary> Gets or sets the version of your application, used in the user agent. </summary> | /// <summary> Gets or sets the version of your application, used in the user agent. </summary> | ||||
public string AppVersion { get { return _appVersion; } set { SetValue(ref _appVersion, value); UpdateUserAgent(); } } | |||||
private string _appVersion = null; | |||||
public string AppVersion { get; set; } = null; | |||||
/// <summary> Gets or sets the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to debug will really hurt performance but should help investigate any internal issues. </summary> | /// <summary> Gets or sets the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to debug will really hurt performance but should help investigate any internal issues. </summary> | ||||
public LogSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } } | |||||
private LogSeverity _logLevel = LogSeverity.Info; | |||||
public LogSeverity LogLevel { get; set; } = LogSeverity.Info; | |||||
/// <summary> Enables or disables the default event logger. </summary> | /// <summary> Enables or disables the default event logger. </summary> | ||||
public bool LogEvents { get { return _logEvents; } set { SetValue(ref _logEvents, value); } } | |||||
private bool _logEvents = true; | |||||
/// <summary> Gets the user agent used when connecting to Discord. </summary> | |||||
public string UserAgent { get; private set; } | |||||
//Rest | |||||
/// <summary> Gets or sets the max time (in milliseconds) to wait for an API request to complete. </summary> | |||||
public int RestTimeout { get { return _restTimeout; } set { SetValue(ref _restTimeout, value); } } | |||||
private int _restTimeout = 10000; | |||||
/// <summary> Enables or disables the internal message queue. This will allow SendMessage/EditMessage to return immediately and handle messages internally. </summary> | |||||
public bool UseMessageQueue { get { return _useMessageQueue; } set { SetValue(ref _useMessageQueue, value); } } | |||||
private bool _useMessageQueue = true; | |||||
/// <summary> Gets or sets the time (in milliseconds) to wait when the message queue is empty before checking again. </summary> | |||||
public int MessageQueueInterval { get { return _messageQueueInterval; } set { SetValue(ref _messageQueueInterval, value); } } | |||||
private int _messageQueueInterval = 100; | |||||
public bool LogEvents { get; set; } = true; | |||||
//WebSocket | //WebSocket | ||||
/// <summary> Gets or sets the time (in milliseconds) to wait for the websocket to connect and initialize. </summary> | /// <summary> Gets or sets the time (in milliseconds) to wait for the websocket to connect and initialize. </summary> | ||||
public int ConnectionTimeout { get { return _connectionTimeout; } set { SetValue(ref _connectionTimeout, value); } } | |||||
private int _connectionTimeout = 30000; | |||||
public int ConnectionTimeout { get; set; } = 30000; | |||||
/// <summary> Gets or sets the time (in milliseconds) to wait after an unexpected disconnect before reconnecting. </summary> | /// <summary> Gets or sets the time (in milliseconds) to wait after an unexpected disconnect before reconnecting. </summary> | ||||
public int ReconnectDelay { get { return _reconnectDelay; } set { SetValue(ref _reconnectDelay, value); } } | |||||
private int _reconnectDelay = 1000; | |||||
public int ReconnectDelay { get; set; } = 1000; | |||||
/// <summary> Gets or sets the time (in milliseconds) to wait after an reconnect fails before retrying. </summary> | /// <summary> Gets or sets the time (in milliseconds) to wait after an reconnect fails before retrying. </summary> | ||||
public int FailedReconnectDelay { get { return _failedReconnectDelay; } set { SetValue(ref _failedReconnectDelay, value); } } | |||||
private int _failedReconnectDelay = 15000; | |||||
/// <summary> Gets or sets the time (in milliseconds) to wait when the websocket's message queue is empty before checking again. </summary> | |||||
public int WebSocketInterval { get { return _webSocketInterval; } set { SetValue(ref _webSocketInterval, value); } } | |||||
private int _webSocketInterval = 100; | |||||
public int FailedReconnectDelay { get; set; } = 15000; | |||||
//Performance | //Performance | ||||
/// <summary> Cache an encrypted login token to temp dir after success login. </summary> | |||||
public bool CacheToken { get { return _cacheToken; } set { SetValue(ref _cacheToken, value); } } | |||||
private bool _cacheToken = true; | |||||
/// <summary> Gets or sets whether an encrypted login token should be saved to temp dir after successful login. </summary> | |||||
public bool CacheToken { get; set; } = true; | |||||
/// <summary> Gets or sets whether Discord should send information about offline users, for servers with more than 100 users. </summary> | /// <summary> Gets or sets whether Discord should send information about offline users, for servers with more than 100 users. </summary> | ||||
public bool UseLargeThreshold { get { return _useLargeThreshold; } set { SetValue(ref _useLargeThreshold, value); } } | |||||
private bool _useLargeThreshold = false; | |||||
public bool UseLargeThreshold { get; set; } = false; | |||||
/// <summary> Gets or sets the number of messages per channel that should be kept in cache. Setting this to zero disables the message cache entirely. </summary> | /// <summary> Gets or sets the number of messages per channel that should be kept in cache. Setting this to zero disables the message cache entirely. </summary> | ||||
public int MessageCacheSize { get { return _messageCacheSize; } set { SetValue(ref _messageCacheSize, value); } } | |||||
private int _messageCacheSize = 100; | |||||
public int MessageCacheSize { get; set; } = 100; | |||||
/// <summary> Gets or sets whether the permissions cache should be used. This makes operations such as User.GetPermissions(Channel), User.ServerPermissions and Channel.Members </summary> | /// <summary> Gets or sets whether the permissions cache should be used. This makes operations such as User.GetPermissions(Channel), User.ServerPermissions and Channel.Members </summary> | ||||
public bool UsePermissionsCache { get { return _usePermissionsCache; } set { SetValue(ref _usePermissionsCache, value); } } | |||||
private bool _usePermissionsCache = true; | |||||
public bool UsePermissionsCache { get; set; } = true; | |||||
/// <summary> Gets or sets whether the a copy of a model is generated on an update event to allow a user to check which properties changed. </summary> | /// <summary> Gets or sets whether the a copy of a model is generated on an update event to allow a user to check which properties changed. </summary> | ||||
public bool EnablePreUpdateEvents { get { return _enablePreUpdateEvents; } set { SetValue(ref _enablePreUpdateEvents, value); } } | |||||
private bool _enablePreUpdateEvents = true; | |||||
public bool EnablePreUpdateEvents { get; set; } = true; | |||||
//Events | |||||
public DiscordConfig() | |||||
/// <summary> Gets or sets a handler for all log messages. </summary> | |||||
public EventHandler<LogMessageEventArgs> LogHandler { get; set; } | |||||
public DiscordConfig Build() => new DiscordConfig(this); | |||||
} | |||||
public class DiscordConfig | |||||
{ | |||||
public const int MaxMessageSize = 2000; | |||||
internal const int RestTimeout = 10000; | |||||
internal const int MessageQueueInterval = 100; | |||||
internal const int WebSocketQueueInterval = 100; | |||||
public const string LibName = "Discord.Net"; | |||||
public static string LibVersion => typeof(DiscordConfigBuilder).GetTypeInfo().Assembly.GetName().Version.ToString(3); | |||||
public const string LibUrl = "https://github.com/RogueException/Discord.Net"; | |||||
public const string ClientAPIUrl = "https://discordapp.com/api/"; | |||||
public const string StatusAPIUrl = "https://srhpyqt94yxb.statuspage.io/api/v2/"; //"https://status.discordapp.com/api/v2/"; | |||||
public const string CDNUrl = "https://cdn.discordapp.com/"; | |||||
public const string InviteUrl = "https://discord.gg/"; | |||||
public LogSeverity LogLevel { get; } | |||||
public bool LogEvents { get; } | |||||
public int ConnectionTimeout { get; } | |||||
public int ReconnectDelay { get; } | |||||
public int FailedReconnectDelay { get; } | |||||
public bool UseLargeThreshold { get; } | |||||
public int MessageCacheSize { get; } | |||||
public bool UsePermissionsCache { get; } | |||||
public bool EnablePreUpdateEvents { get; } | |||||
public string UserAgent { get; } | |||||
public string CacheDir { get; } | |||||
internal DiscordConfig(DiscordConfigBuilder builder) | |||||
{ | { | ||||
UpdateUserAgent(); | |||||
LogLevel = builder.LogLevel; | |||||
LogEvents = builder.LogEvents; | |||||
ConnectionTimeout = builder.ConnectionTimeout; | |||||
ReconnectDelay = builder.ReconnectDelay; | |||||
FailedReconnectDelay = builder.FailedReconnectDelay; | |||||
UseLargeThreshold = builder.UseLargeThreshold; | |||||
MessageCacheSize = builder.MessageCacheSize; | |||||
UsePermissionsCache = builder.UsePermissionsCache; | |||||
EnablePreUpdateEvents = builder.EnablePreUpdateEvents; | |||||
UserAgent = GetUserAgent(builder); | |||||
CacheDir = GetCacheDir(builder); | |||||
} | } | ||||
private void UpdateUserAgent() | |||||
private string GetUserAgent(DiscordConfigBuilder builder) | |||||
{ | { | ||||
StringBuilder builder = new StringBuilder(); | |||||
if (!string.IsNullOrEmpty(_appName)) | |||||
StringBuilder sb = new StringBuilder(); | |||||
if (!string.IsNullOrEmpty(builder.AppName)) | |||||
{ | { | ||||
builder.Append(_appName); | |||||
if (!string.IsNullOrEmpty(_appVersion)) | |||||
sb.Append(builder.AppName); | |||||
if (!string.IsNullOrEmpty(builder.AppVersion)) | |||||
{ | { | ||||
builder.Append('/'); | |||||
builder.Append(_appVersion); | |||||
sb.Append('/'); | |||||
sb.Append(builder.AppVersion); | |||||
} | } | ||||
if (!string.IsNullOrEmpty(_appUrl)) | |||||
if (!string.IsNullOrEmpty(builder.AppUrl)) | |||||
{ | { | ||||
builder.Append(" ("); | |||||
builder.Append(_appUrl); | |||||
builder.Append(')'); | |||||
sb.Append(" ("); | |||||
sb.Append(builder.AppUrl); | |||||
sb.Append(')'); | |||||
} | } | ||||
builder.Append(' '); | |||||
sb.Append(' '); | |||||
} | } | ||||
builder.Append($"DiscordBot ({LibUrl}, v{LibVersion})"); | |||||
UserAgent = builder.ToString(); | |||||
sb.Append($"DiscordBot ({LibUrl}, v{LibVersion})"); | |||||
return sb.ToString(); | |||||
} | |||||
private string GetCacheDir(DiscordConfigBuilder builder) | |||||
{ | |||||
if (builder.CacheToken) | |||||
return Path.Combine(Path.GetTempPath(), builder.AppName ?? "Discord.Net"); | |||||
else | |||||
return null; | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -0,0 +1,11 @@ | |||||
namespace Discord | |||||
{ | |||||
public enum LogSeverity : byte | |||||
{ | |||||
Error = 1, | |||||
Warning = 2, | |||||
Info = 3, | |||||
Verbose = 4, | |||||
Debug = 5 | |||||
} | |||||
} |
@@ -99,10 +99,10 @@ namespace Discord.Net | |||||
_pendingActions.Enqueue(new DeleteAction(msg)); | _pendingActions.Enqueue(new DeleteAction(msg)); | ||||
} | } | ||||
internal Task Run(CancellationToken cancelToken, int interval) | |||||
internal Task Run(CancellationToken cancelToken) | |||||
{ | { | ||||
_nextWarning = WarningStart; | _nextWarning = WarningStart; | ||||
return Task.Run(async () => | |||||
return Task.Run((Func<Task>)(async () => | |||||
{ | { | ||||
try | try | ||||
{ | { | ||||
@@ -121,11 +121,11 @@ namespace Discord.Net | |||||
while (_pendingActions.TryDequeue(out queuedAction)) | while (_pendingActions.TryDequeue(out queuedAction)) | ||||
await queuedAction.Do(this).ConfigureAwait(false); | await queuedAction.Do(this).ConfigureAwait(false); | ||||
await Task.Delay(interval).ConfigureAwait(false); | |||||
await Task.Delay((int)Discord.DiscordConfig.MessageQueueInterval).ConfigureAwait(false); | |||||
} | } | ||||
} | } | ||||
catch (OperationCanceledException) { } | catch (OperationCanceledException) { } | ||||
}); | |||||
})); | |||||
} | } | ||||
internal async Task Send(Message msg) | internal async Task Send(Message msg) | ||||
@@ -343,26 +343,12 @@ namespace Discord | |||||
if (text == "") throw new ArgumentException("Value cannot be blank", nameof(text)); | if (text == "") throw new ArgumentException("Value cannot be blank", nameof(text)); | ||||
return SendMessageInternal(text, true); | return SendMessageInternal(text, true); | ||||
} | } | ||||
private async Task<Message> SendMessageInternal(string text, bool isTTS) | |||||
private Task<Message> SendMessageInternal(string text, bool isTTS) | |||||
{ | { | ||||
if (text.Length > DiscordConfig.MaxMessageSize) | if (text.Length > DiscordConfig.MaxMessageSize) | ||||
throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {DiscordConfig.MaxMessageSize} characters or less."); | throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {DiscordConfig.MaxMessageSize} characters or less."); | ||||
if (Client.Config.UseMessageQueue) | |||||
return Client.MessageQueue.QueueSend(this, text, isTTS); | |||||
else | |||||
{ | |||||
var request = new SendMessageRequest(Id) | |||||
{ | |||||
Content = text, | |||||
Nonce = null, | |||||
IsTTS = isTTS | |||||
}; | |||||
var model = await Client.ClientAPI.Send(request).ConfigureAwait(false); | |||||
var msg = AddMessage(model.Id, IsPrivate ? Client.PrivateUser : Server.CurrentUser, model.Timestamp.Value); | |||||
msg.Update(model); | |||||
return msg; | |||||
} | |||||
return Task.FromResult(Client.MessageQueue.QueueSend(this, text, isTTS)); | |||||
} | } | ||||
public async Task<Message> SendFile(string filePath) | public async Task<Message> SendFile(string filePath) | ||||
@@ -310,7 +310,7 @@ namespace Discord | |||||
} | } | ||||
} | } | ||||
public async Task Edit(string text) | |||||
public Task Edit(string text) | |||||
{ | { | ||||
if (text == null) throw new ArgumentNullException(nameof(text)); | if (text == null) throw new ArgumentNullException(nameof(text)); | ||||
@@ -318,28 +318,14 @@ namespace Discord | |||||
if (text.Length > DiscordConfig.MaxMessageSize) | if (text.Length > DiscordConfig.MaxMessageSize) | ||||
throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {DiscordConfig.MaxMessageSize} characters or less."); | throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {DiscordConfig.MaxMessageSize} characters or less."); | ||||
if (Client.Config.UseMessageQueue) | |||||
Client.MessageQueue.QueueEdit(this, text); | |||||
else | |||||
{ | |||||
var request = new UpdateMessageRequest(Channel.Id, Id) | |||||
{ | |||||
Content = text | |||||
}; | |||||
await Client.ClientAPI.Send(request).ConfigureAwait(false); | |||||
} | |||||
Client.MessageQueue.QueueEdit(this, text); | |||||
return TaskHelper.CompletedTask; | |||||
} | } | ||||
public async Task Delete() | |||||
public Task Delete() | |||||
{ | { | ||||
if (Client.Config.UseMessageQueue) | |||||
Client.MessageQueue.QueueDelete(this); | |||||
else | |||||
{ | |||||
var request = new DeleteMessageRequest(Channel.Id, Id); | |||||
try { await Client.ClientAPI.Send(request).ConfigureAwait(false); } | |||||
catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } | |||||
} | |||||
Client.MessageQueue.QueueDelete(this); | |||||
return TaskHelper.CompletedTask; | |||||
} | } | ||||
/// <summary> Returns true if the logged-in user was mentioned. </summary> | /// <summary> Returns true if the logged-in user was mentioned. </summary> | ||||
@@ -31,7 +31,7 @@ namespace Discord.Net.Rest | |||||
_client = new RestSharpClient(baseUrl) | _client = new RestSharpClient(baseUrl) | ||||
{ | { | ||||
PreAuthenticate = false, | PreAuthenticate = false, | ||||
ReadWriteTimeout = _config.RestTimeout, | |||||
ReadWriteTimeout = DiscordConfig.RestTimeout, | |||||
UserAgent = config.UserAgent | UserAgent = config.UserAgent | ||||
}; | }; | ||||
_client.Proxy = null; | _client.Proxy = null; | ||||
@@ -65,9 +65,7 @@ namespace Discord.Net.WebSockets | |||||
{ | { | ||||
return Task.Run(async () => | return Task.Run(async () => | ||||
{ | { | ||||
var sendInterval = _config.WebSocketInterval; | |||||
//var buffer = new ArraySegment<byte>(new byte[ReceiveChunkSize]); | |||||
var buffer = new byte[ReceiveChunkSize]; | |||||
var buffer = new ArraySegment<byte>(new byte[ReceiveChunkSize]); | |||||
var stream = new MemoryStream(); | var stream = new MemoryStream(); | ||||
try | try | ||||
@@ -81,7 +79,7 @@ namespace Discord.Net.WebSockets | |||||
try | try | ||||
{ | { | ||||
result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancelToken).ConfigureAwait(false); | |||||
result = await _webSocket.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false); | |||||
} | } | ||||
catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) | catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) | ||||
{ | { | ||||
@@ -91,7 +89,7 @@ namespace Discord.Net.WebSockets | |||||
if (result.MessageType == WebSocketMessageType.Close) | if (result.MessageType == WebSocketMessageType.Close) | ||||
throw new WebSocketException((int)result.CloseStatus.Value, result.CloseStatusDescription); | throw new WebSocketException((int)result.CloseStatus.Value, result.CloseStatusDescription); | ||||
else | else | ||||
stream.Write(buffer, 0, result.Count); | |||||
stream.Write(buffer.Array, buffer.Offset, buffer.Count); | |||||
} | } | ||||
while (result == null || !result.EndOfMessage); | while (result == null || !result.EndOfMessage); | ||||
@@ -114,7 +112,6 @@ namespace Discord.Net.WebSockets | |||||
return Task.Run(async () => | return Task.Run(async () => | ||||
{ | { | ||||
byte[] bytes = new byte[SendChunkSize]; | byte[] bytes = new byte[SendChunkSize]; | ||||
var sendInterval = _config.WebSocketInterval; | |||||
try | try | ||||
{ | { | ||||
@@ -147,7 +144,7 @@ namespace Discord.Net.WebSockets | |||||
} | } | ||||
} | } | ||||
} | } | ||||
await Task.Delay(sendInterval, cancelToken).ConfigureAwait(false); | |||||
await Task.Delay(DiscordConfig.WebSocketQueueInterval, cancelToken).ConfigureAwait(false); | |||||
} | } | ||||
} | } | ||||
catch (OperationCanceledException) { } | catch (OperationCanceledException) { } | ||||
@@ -118,7 +118,6 @@ namespace Discord.Net.WebSockets | |||||
private Task SendAsync(CancellationToken cancelToken) | private Task SendAsync(CancellationToken cancelToken) | ||||
{ | { | ||||
var sendInterval = _config.WebSocketInterval; | |||||
return Task.Run(async () => | return Task.Run(async () => | ||||
{ | { | ||||
try | try | ||||
@@ -128,7 +127,7 @@ namespace Discord.Net.WebSockets | |||||
string json; | string json; | ||||
while (_sendQueue.TryDequeue(out json)) | while (_sendQueue.TryDequeue(out json)) | ||||
_webSocket.Send(json); | _webSocket.Send(json); | ||||
await Task.Delay(sendInterval, cancelToken).ConfigureAwait(false); | |||||
await Task.Delay(DiscordConfig.WebSocketQueueInterval, cancelToken).ConfigureAwait(false); | |||||
} | } | ||||
} | } | ||||
catch (OperationCanceledException) { } | catch (OperationCanceledException) { } | ||||