@@ -151,6 +151,9 @@ | |||||
<Compile Include="..\Discord.Net\DiscordAPIClient.cs"> | <Compile Include="..\Discord.Net\DiscordAPIClient.cs"> | ||||
<Link>DiscordAPIClient.cs</Link> | <Link>DiscordAPIClient.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\DiscordAPIClientConfig.cs"> | |||||
<Link>DiscordAPIClientConfig.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\DiscordClient.API.cs"> | <Compile Include="..\Discord.Net\DiscordClient.API.cs"> | ||||
<Link>DiscordClient.API.cs</Link> | <Link>DiscordClient.API.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -11,12 +11,15 @@ namespace Discord | |||||
/// <summary> A lightweight wrapper around the Discord API. </summary> | /// <summary> A lightweight wrapper around the Discord API. </summary> | ||||
public class DiscordAPIClient | public class DiscordAPIClient | ||||
{ | { | ||||
private readonly DiscordAPIClientConfig _config; | |||||
internal RestClient RestClient => _rest; | internal RestClient RestClient => _rest; | ||||
private readonly RestClient _rest; | private readonly RestClient _rest; | ||||
public DiscordAPIClient(LogMessageSeverity logLevel, string userAgent, int timeout) | |||||
public DiscordAPIClient(DiscordAPIClientConfig config = null) | |||||
{ | { | ||||
_rest = new RestClient(logLevel, userAgent, timeout); | |||||
_config = config ?? new DiscordAPIClientConfig(); | |||||
_rest = new RestClient(_config); | |||||
} | } | ||||
private string _token; | private string _token; | ||||
@@ -0,0 +1,50 @@ | |||||
using System; | |||||
using System.Net; | |||||
using System.Reflection; | |||||
namespace Discord | |||||
{ | |||||
public class DiscordAPIClientConfig | |||||
{ | |||||
/// <summary> Specifies 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 LogMessageSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } } | |||||
private LogMessageSeverity _logLevel = LogMessageSeverity.Info; | |||||
/// <summary> Max time (in milliseconds) to wait for an API request to complete. </summary> | |||||
public int APITimeout { get { return _apiTimeout; } set { SetValue(ref _apiTimeout, value); } } | |||||
private int _apiTimeout = 10000; | |||||
/// <summary> The proxy to user for API and WebSocket connections. </summary> | |||||
public string ProxyUrl { get { return _proxyUrl; } set { SetValue(ref _proxyUrl, value); } } | |||||
private string _proxyUrl = null; | |||||
/// <summary> The credentials to use for this proxy. </summary> | |||||
public NetworkCredential ProxyCredentials { get { return _proxyCredentials; } set { SetValue(ref _proxyCredentials, value); } } | |||||
private NetworkCredential _proxyCredentials = null; | |||||
internal string UserAgent | |||||
{ | |||||
get | |||||
{ | |||||
string version = typeof(DiscordClientConfig).GetTypeInfo().Assembly.GetName().Version.ToString(2); | |||||
return $"Discord.Net/{version} (https://github.com/RogueException/Discord.Net)"; | |||||
} | |||||
} | |||||
//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 discord client's configuration after it has been created."); | |||||
storage = value; | |||||
} | |||||
public DiscordAPIClientConfig Clone() | |||||
{ | |||||
var config = MemberwiseClone() as DiscordAPIClientConfig; | |||||
config._isLocked = false; | |||||
return config; | |||||
} | |||||
} | |||||
} |
@@ -55,7 +55,7 @@ namespace Discord | |||||
: base(config ?? new DiscordClientConfig()) | : base(config ?? new DiscordClientConfig()) | ||||
{ | { | ||||
_rand = new Random(); | _rand = new Random(); | ||||
_api = new DiscordAPIClient(_config.LogLevel, _config.UserAgent, _config.APITimeout); | |||||
_api = new DiscordAPIClient(_config); | |||||
if (Config.UseMessageQueue) | if (Config.UseMessageQueue) | ||||
_pendingMessages = new ConcurrentQueue<Message>(); | _pendingMessages = new ConcurrentQueue<Message>(); | ||||
if (Config.EnableVoiceMultiserver) | if (Config.EnableVoiceMultiserver) | ||||
@@ -765,7 +765,6 @@ namespace Discord | |||||
{ | { | ||||
var config = _config.Clone(); | var config = _config.Clone(); | ||||
config.LogLevel = _config.LogLevel;// (LogMessageSeverity)Math.Min((int)_config.LogLevel, (int)LogMessageSeverity.Warning); | config.LogLevel = _config.LogLevel;// (LogMessageSeverity)Math.Min((int)_config.LogLevel, (int)LogMessageSeverity.Warning); | ||||
config.EnableVoiceMultiserver = false; | |||||
config.VoiceOnly = true; | config.VoiceOnly = true; | ||||
config.VoiceClientId = unchecked(++_nextVoiceClientId); | config.VoiceClientId = unchecked(++_nextVoiceClientId); | ||||
return new DiscordWebSocketClient(config, serverId); | return new DiscordWebSocketClient(config, serverId); | ||||
@@ -25,7 +25,7 @@ | |||||
public new DiscordClientConfig Clone() | public new DiscordClientConfig Clone() | ||||
{ | { | ||||
var config = this.MemberwiseClone() as DiscordClientConfig; | |||||
var config = MemberwiseClone() as DiscordClientConfig; | |||||
config._isLocked = false; | config._isLocked = false; | ||||
return config; | return config; | ||||
} | } | ||||
@@ -12,12 +12,8 @@ namespace Discord | |||||
Both = Outgoing | Incoming | Both = Outgoing | Incoming | ||||
} | } | ||||
public class DiscordWebSocketClientConfig | |||||
public class DiscordWebSocketClientConfig : DiscordAPIClientConfig | |||||
{ | { | ||||
/// <summary> Specifies 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 LogMessageSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } } | |||||
private LogMessageSeverity _logLevel = LogMessageSeverity.Info; | |||||
/// <summary> Max time in milliseconds to wait for DiscordClient to connect and initialize. </summary> | /// <summary> Max time in milliseconds to wait for DiscordClient to connect and initialize. </summary> | ||||
public int ConnectionTimeout { get { return _connectionTimeout; } set { SetValue(ref _connectionTimeout, value); } } | public int ConnectionTimeout { get { return _connectionTimeout; } set { SetValue(ref _connectionTimeout, value); } } | ||||
private int _connectionTimeout = 30000; | private int _connectionTimeout = 30000; | ||||
@@ -27,9 +23,6 @@ namespace Discord | |||||
/// <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); } } | public int FailedReconnectDelay { get { return _failedReconnectDelay; } set { SetValue(ref _failedReconnectDelay, value); } } | ||||
private int _failedReconnectDelay = 10000; | private int _failedReconnectDelay = 10000; | ||||
/// <summary> Max time (in milliseconds) to wait for an API request to complete. </summary> | |||||
public int APITimeout { get { return _apiTimeout; } set { SetValue(ref _apiTimeout, value); } } | |||||
private int _apiTimeout = 10000; | |||||
/// <summary> Gets or sets the time (in milliseconds) to wait when the websocket's message queue is empty before checking again. </summary> | /// <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); } } | public int WebSocketInterval { get { return _webSocketInterval; } set { SetValue(ref _webSocketInterval, value); } } | ||||
@@ -54,28 +47,9 @@ namespace Discord | |||||
internal virtual bool EnableVoice => _voiceMode != DiscordVoiceMode.Disabled; | internal virtual bool EnableVoice => _voiceMode != DiscordVoiceMode.Disabled; | ||||
internal string UserAgent | |||||
{ | |||||
get | |||||
{ | |||||
string version = typeof(DiscordClientConfig).GetTypeInfo().Assembly.GetName().Version.ToString(2); | |||||
return $"Discord.Net/{version} (https://github.com/RogueException/Discord.Net)"; | |||||
} | |||||
} | |||||
//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 discord client's configuration after it has been created."); | |||||
storage = value; | |||||
} | |||||
public DiscordClientConfig Clone() | |||||
public new DiscordWebSocketClientConfig Clone() | |||||
{ | { | ||||
var config = this.MemberwiseClone() as DiscordClientConfig; | |||||
var config = MemberwiseClone() as DiscordWebSocketClientConfig; | |||||
config._isLocked = false; | config._isLocked = false; | ||||
return config; | return config; | ||||
} | } | ||||
@@ -1,6 +1,7 @@ | |||||
using Discord.API; | using Discord.API; | ||||
using RestSharp; | using RestSharp; | ||||
using System; | using System; | ||||
using System.Net; | |||||
using System.Net.Http; | using System.Net.Http; | ||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
@@ -11,18 +12,19 @@ namespace Discord.Net | |||||
{ | { | ||||
private RestSharp.RestClient _client; | private RestSharp.RestClient _client; | ||||
partial void Initialize(string userAgent, int timeout) | |||||
partial void Initialize() | |||||
{ | { | ||||
_client = new RestSharp.RestClient(Endpoints.BaseApi) | _client = new RestSharp.RestClient(Endpoints.BaseApi) | ||||
{ | { | ||||
PreAuthenticate = false | |||||
PreAuthenticate = false, | |||||
Proxy = new WebProxy(_config.ProxyUrl, true, new string[0], _config.ProxyCredentials), | |||||
ReadWriteTimeout = _config.APITimeout, | |||||
UserAgent = _config.UserAgent | |||||
}; | }; | ||||
_client.RemoveDefaultParameter("Accept"); | _client.RemoveDefaultParameter("Accept"); | ||||
_client.AddDefaultHeader("accept", "*/*"); | _client.AddDefaultHeader("accept", "*/*"); | ||||
_client.AddDefaultHeader("accept-encoding", "gzip,deflate"); | _client.AddDefaultHeader("accept-encoding", "gzip,deflate"); | ||||
_client.UserAgent = userAgent; | |||||
_client.ReadWriteTimeout = timeout; | |||||
} | |||||
} | |||||
internal void SetToken(string token) | internal void SetToken(string token) | ||||
{ | { | ||||
@@ -10,15 +10,15 @@ namespace Discord.Net | |||||
{ | { | ||||
internal partial class RestClient | internal partial class RestClient | ||||
{ | { | ||||
private readonly LogMessageSeverity _logLevel; | |||||
private readonly DiscordAPIClientConfig _config; | |||||
private CancellationToken _cancelToken; | private CancellationToken _cancelToken; | ||||
public RestClient(LogMessageSeverity logLevel, string userAgent, int timeout) | |||||
public RestClient(DiscordAPIClientConfig config) | |||||
{ | { | ||||
_logLevel = logLevel; | |||||
Initialize(userAgent, timeout); | |||||
_config = config; | |||||
Initialize(); | |||||
} | } | ||||
partial void Initialize(string userAgent, int timeout); | |||||
partial void Initialize(); | |||||
//DELETE | //DELETE | ||||
private static readonly HttpMethod _delete = HttpMethod.Delete; | private static readonly HttpMethod _delete = HttpMethod.Delete; | ||||
@@ -90,7 +90,7 @@ namespace Discord.Net | |||||
if (content != null) | if (content != null) | ||||
requestJson = JsonConvert.SerializeObject(content); | requestJson = JsonConvert.SerializeObject(content); | ||||
if (_logLevel >= LogMessageSeverity.Verbose) | |||||
if (_config.LogLevel >= LogMessageSeverity.Verbose) | |||||
stopwatch = Stopwatch.StartNew(); | stopwatch = Stopwatch.StartNew(); | ||||
string responseJson = await SendInternal(method, path, requestJson, _cancelToken).ConfigureAwait(false); | string responseJson = await SendInternal(method, path, requestJson, _cancelToken).ConfigureAwait(false); | ||||
@@ -100,10 +100,10 @@ namespace Discord.Net | |||||
throw new Exception("API check failed: Response is not empty."); | throw new Exception("API check failed: Response is not empty."); | ||||
#endif | #endif | ||||
if (_logLevel >= LogMessageSeverity.Verbose) | |||||
if (_config.LogLevel >= LogMessageSeverity.Verbose) | |||||
{ | { | ||||
stopwatch.Stop(); | stopwatch.Stop(); | ||||
if (content != null && _logLevel >= LogMessageSeverity.Debug) | |||||
if (content != null && _config.LogLevel >= LogMessageSeverity.Debug) | |||||
{ | { | ||||
if (path.StartsWith(Endpoints.Auth)) | if (path.StartsWith(Endpoints.Auth)) | ||||
RaiseOnRequest(method, path, "[Hidden]", stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); | RaiseOnRequest(method, path, "[Hidden]", stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); | ||||
@@ -129,7 +129,7 @@ namespace Discord.Net | |||||
{ | { | ||||
Stopwatch stopwatch = null; | Stopwatch stopwatch = null; | ||||
if (_logLevel >= LogMessageSeverity.Verbose) | |||||
if (_config.LogLevel >= LogMessageSeverity.Verbose) | |||||
stopwatch = Stopwatch.StartNew(); | stopwatch = Stopwatch.StartNew(); | ||||
string responseJson = await SendFileInternal(method, path, filePath, _cancelToken).ConfigureAwait(false); | string responseJson = await SendFileInternal(method, path, filePath, _cancelToken).ConfigureAwait(false); | ||||
@@ -139,10 +139,10 @@ namespace Discord.Net | |||||
throw new Exception("API check failed: Response is not empty."); | throw new Exception("API check failed: Response is not empty."); | ||||
#endif | #endif | ||||
if (_logLevel >= LogMessageSeverity.Verbose) | |||||
if (_config.LogLevel >= LogMessageSeverity.Verbose) | |||||
{ | { | ||||
stopwatch.Stop(); | stopwatch.Stop(); | ||||
if (_logLevel >= LogMessageSeverity.Debug) | |||||
if (_config.LogLevel >= LogMessageSeverity.Debug) | |||||
RaiseOnRequest(method, path, filePath, stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); | RaiseOnRequest(method, path, filePath, stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); | ||||
else | else | ||||
RaiseOnRequest(method, path, null, stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); | RaiseOnRequest(method, path, null, stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); | ||||
@@ -1,6 +1,7 @@ | |||||
using System; | using System; | ||||
using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Net; | |||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using WSSharpNWebSocket = WebSocketSharp.WebSocket; | using WSSharpNWebSocket = WebSocketSharp.WebSocket; | ||||
@@ -9,9 +10,8 @@ namespace Discord.Net | |||||
{ | { | ||||
internal class WSSharpWebSocketEngine : IWebSocketEngine | internal class WSSharpWebSocketEngine : IWebSocketEngine | ||||
{ | { | ||||
private readonly DiscordWebSocketClientConfig _config; | |||||
private readonly ConcurrentQueue<string> _sendQueue; | private readonly ConcurrentQueue<string> _sendQueue; | ||||
private readonly int _sendInterval; | |||||
private readonly string _userAgent; | |||||
private readonly WebSocket _parent; | private readonly WebSocket _parent; | ||||
private WSSharpNWebSocket _webSocket; | private WSSharpNWebSocket _webSocket; | ||||
@@ -22,11 +22,10 @@ namespace Discord.Net | |||||
ProcessMessage(this, new WebSocketMessageEventArgs(msg)); | ProcessMessage(this, new WebSocketMessageEventArgs(msg)); | ||||
} | } | ||||
internal WSSharpWebSocketEngine(WebSocket parent, string userAgent, int sendInterval) | |||||
internal WSSharpWebSocketEngine(WebSocket parent, DiscordWebSocketClientConfig config) | |||||
{ | { | ||||
_parent = parent; | _parent = parent; | ||||
_userAgent = userAgent; | |||||
_sendInterval = sendInterval; | |||||
_config = config; | |||||
_sendQueue = new ConcurrentQueue<string>(); | _sendQueue = new ConcurrentQueue<string>(); | ||||
} | } | ||||
@@ -35,7 +34,8 @@ namespace Discord.Net | |||||
_webSocket = new WSSharpNWebSocket(host); | _webSocket = new WSSharpNWebSocket(host); | ||||
_webSocket.EmitOnPing = false; | _webSocket.EmitOnPing = false; | ||||
_webSocket.EnableRedirection = true; | _webSocket.EnableRedirection = true; | ||||
_webSocket.Compression = WebSocketSharp.CompressionMethod.None; | |||||
_webSocket.Compression = WebSocketSharp.CompressionMethod.None; | |||||
_webSocket.SetProxy(_config.ProxyUrl, _config.ProxyCredentials.UserName, _config.ProxyCredentials.Password); | |||||
_webSocket.OnMessage += (s, e) => RaiseProcessMessage(e.Data); | _webSocket.OnMessage += (s, e) => RaiseProcessMessage(e.Data); | ||||
_webSocket.OnError += async (s, e) => | _webSocket.OnError += async (s, e) => | ||||
{ | { | ||||
@@ -77,6 +77,7 @@ namespace Discord.Net | |||||
private Task SendAsync(CancellationToken cancelToken) | private Task SendAsync(CancellationToken cancelToken) | ||||
{ | { | ||||
var sendInterval = _config.WebSocketInterval; | |||||
return Task.Run(async () => | return Task.Run(async () => | ||||
{ | { | ||||
try | try | ||||
@@ -86,7 +87,7 @@ namespace Discord.Net | |||||
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(sendInterval, cancelToken).ConfigureAwait(false); | |||||
} | } | ||||
} | } | ||||
catch (OperationCanceledException) { } | catch (OperationCanceledException) { } | ||||
@@ -65,7 +65,7 @@ namespace Discord.Net | |||||
_cancelToken = new CancellationToken(true); | _cancelToken = new CancellationToken(true); | ||||
_connectedEvent = new ManualResetEventSlim(false); | _connectedEvent = new ManualResetEventSlim(false); | ||||
_engine = new WSSharpWebSocketEngine(this, client.Config.UserAgent, client.Config.WebSocketInterval); | |||||
_engine = new WSSharpWebSocketEngine(this, client.Config); | |||||
_engine.ProcessMessage += async (s, e) => | _engine.ProcessMessage += async (s, e) => | ||||
{ | { | ||||
if (_logLevel >= LogMessageSeverity.Debug) | if (_logLevel >= LogMessageSeverity.Debug) | ||||