| @@ -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) { } | ||||