diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index 703014726..11f2c1766 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -151,6 +151,9 @@ DiscordAPIClient.cs + + DiscordAPIClientConfig.cs + DiscordClient.API.cs diff --git a/src/Discord.Net/DiscordAPIClient.cs b/src/Discord.Net/DiscordAPIClient.cs index 81a50114e..975b9aa6d 100644 --- a/src/Discord.Net/DiscordAPIClient.cs +++ b/src/Discord.Net/DiscordAPIClient.cs @@ -11,12 +11,15 @@ namespace Discord /// A lightweight wrapper around the Discord API. public class DiscordAPIClient { + private readonly DiscordAPIClientConfig _config; + internal RestClient 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; diff --git a/src/Discord.Net/DiscordAPIClientConfig.cs b/src/Discord.Net/DiscordAPIClientConfig.cs new file mode 100644 index 000000000..0fde36c2c --- /dev/null +++ b/src/Discord.Net/DiscordAPIClientConfig.cs @@ -0,0 +1,50 @@ +using System; +using System.Net; +using System.Reflection; + +namespace Discord +{ + public class DiscordAPIClientConfig + { + /// 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. + public LogMessageSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } } + private LogMessageSeverity _logLevel = LogMessageSeverity.Info; + + /// Max time (in milliseconds) to wait for an API request to complete. + public int APITimeout { get { return _apiTimeout; } set { SetValue(ref _apiTimeout, value); } } + private int _apiTimeout = 10000; + + /// The proxy to user for API and WebSocket connections. + public string ProxyUrl { get { return _proxyUrl; } set { SetValue(ref _proxyUrl, value); } } + private string _proxyUrl = null; + /// The credentials to use for this proxy. + 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(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; + } + } +} \ No newline at end of file diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index d402ed573..8a2709a8d 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -55,7 +55,7 @@ namespace Discord : base(config ?? new DiscordClientConfig()) { _rand = new Random(); - _api = new DiscordAPIClient(_config.LogLevel, _config.UserAgent, _config.APITimeout); + _api = new DiscordAPIClient(_config); if (Config.UseMessageQueue) _pendingMessages = new ConcurrentQueue(); if (Config.EnableVoiceMultiserver) @@ -765,7 +765,6 @@ namespace Discord { var config = _config.Clone(); config.LogLevel = _config.LogLevel;// (LogMessageSeverity)Math.Min((int)_config.LogLevel, (int)LogMessageSeverity.Warning); - config.EnableVoiceMultiserver = false; config.VoiceOnly = true; config.VoiceClientId = unchecked(++_nextVoiceClientId); return new DiscordWebSocketClient(config, serverId); diff --git a/src/Discord.Net/DiscordClientConfig.cs b/src/Discord.Net/DiscordClientConfig.cs index 6396b5c82..e5f2cdbac 100644 --- a/src/Discord.Net/DiscordClientConfig.cs +++ b/src/Discord.Net/DiscordClientConfig.cs @@ -25,7 +25,7 @@ public new DiscordClientConfig Clone() { - var config = this.MemberwiseClone() as DiscordClientConfig; + var config = MemberwiseClone() as DiscordClientConfig; config._isLocked = false; return config; } diff --git a/src/Discord.Net/DiscordWebSocketClientConfig.cs b/src/Discord.Net/DiscordWebSocketClientConfig.cs index b788b3a7b..601ad8c21 100644 --- a/src/Discord.Net/DiscordWebSocketClientConfig.cs +++ b/src/Discord.Net/DiscordWebSocketClientConfig.cs @@ -12,12 +12,8 @@ namespace Discord Both = Outgoing | Incoming } - public class DiscordWebSocketClientConfig + public class DiscordWebSocketClientConfig : DiscordAPIClientConfig { - /// 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. - public LogMessageSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } } - private LogMessageSeverity _logLevel = LogMessageSeverity.Info; - /// Max time in milliseconds to wait for DiscordClient to connect and initialize. public int ConnectionTimeout { get { return _connectionTimeout; } set { SetValue(ref _connectionTimeout, value); } } private int _connectionTimeout = 30000; @@ -27,9 +23,6 @@ namespace Discord /// Gets or sets the time (in milliseconds) to wait after an reconnect fails before retrying. public int FailedReconnectDelay { get { return _failedReconnectDelay; } set { SetValue(ref _failedReconnectDelay, value); } } private int _failedReconnectDelay = 10000; - /// Max time (in milliseconds) to wait for an API request to complete. - public int APITimeout { get { return _apiTimeout; } set { SetValue(ref _apiTimeout, value); } } - private int _apiTimeout = 10000; /// Gets or sets the time (in milliseconds) to wait when the websocket's message queue is empty before checking again. 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 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(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; return config; } diff --git a/src/Discord.Net/Net/RestClient.SharpRest.cs b/src/Discord.Net/Net/RestClient.SharpRest.cs index 31e4f4925..1a9955233 100644 --- a/src/Discord.Net/Net/RestClient.SharpRest.cs +++ b/src/Discord.Net/Net/RestClient.SharpRest.cs @@ -1,6 +1,7 @@ using Discord.API; using RestSharp; using System; +using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -11,18 +12,19 @@ namespace Discord.Net { private RestSharp.RestClient _client; - partial void Initialize(string userAgent, int timeout) + partial void Initialize() { _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.AddDefaultHeader("accept", "*/*"); _client.AddDefaultHeader("accept-encoding", "gzip,deflate"); - _client.UserAgent = userAgent; - _client.ReadWriteTimeout = timeout; - } + } internal void SetToken(string token) { diff --git a/src/Discord.Net/Net/RestClient.cs b/src/Discord.Net/Net/RestClient.cs index c956728c9..e8fa94db7 100644 --- a/src/Discord.Net/Net/RestClient.cs +++ b/src/Discord.Net/Net/RestClient.cs @@ -10,15 +10,15 @@ namespace Discord.Net { internal partial class RestClient { - private readonly LogMessageSeverity _logLevel; + private readonly DiscordAPIClientConfig _config; 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 private static readonly HttpMethod _delete = HttpMethod.Delete; @@ -90,7 +90,7 @@ namespace Discord.Net if (content != null) requestJson = JsonConvert.SerializeObject(content); - if (_logLevel >= LogMessageSeverity.Verbose) + if (_config.LogLevel >= LogMessageSeverity.Verbose) stopwatch = Stopwatch.StartNew(); 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."); #endif - if (_logLevel >= LogMessageSeverity.Verbose) + if (_config.LogLevel >= LogMessageSeverity.Verbose) { stopwatch.Stop(); - if (content != null && _logLevel >= LogMessageSeverity.Debug) + if (content != null && _config.LogLevel >= LogMessageSeverity.Debug) { if (path.StartsWith(Endpoints.Auth)) RaiseOnRequest(method, path, "[Hidden]", stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); @@ -129,7 +129,7 @@ namespace Discord.Net { Stopwatch stopwatch = null; - if (_logLevel >= LogMessageSeverity.Verbose) + if (_config.LogLevel >= LogMessageSeverity.Verbose) stopwatch = Stopwatch.StartNew(); 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."); #endif - if (_logLevel >= LogMessageSeverity.Verbose) + if (_config.LogLevel >= LogMessageSeverity.Verbose) { stopwatch.Stop(); - if (_logLevel >= LogMessageSeverity.Debug) + if (_config.LogLevel >= LogMessageSeverity.Debug) RaiseOnRequest(method, path, filePath, stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); else RaiseOnRequest(method, path, null, stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond); diff --git a/src/Discord.Net/Net/WebSocket.WebSocketSharp.cs b/src/Discord.Net/Net/WebSocket.WebSocketSharp.cs index f245dd24e..7a0ac2c30 100644 --- a/src/Discord.Net/Net/WebSocket.WebSocketSharp.cs +++ b/src/Discord.Net/Net/WebSocket.WebSocketSharp.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Net; using System.Threading; using System.Threading.Tasks; using WSSharpNWebSocket = WebSocketSharp.WebSocket; @@ -9,9 +10,8 @@ namespace Discord.Net { internal class WSSharpWebSocketEngine : IWebSocketEngine { + private readonly DiscordWebSocketClientConfig _config; private readonly ConcurrentQueue _sendQueue; - private readonly int _sendInterval; - private readonly string _userAgent; private readonly WebSocket _parent; private WSSharpNWebSocket _webSocket; @@ -22,11 +22,10 @@ namespace Discord.Net ProcessMessage(this, new WebSocketMessageEventArgs(msg)); } - internal WSSharpWebSocketEngine(WebSocket parent, string userAgent, int sendInterval) + internal WSSharpWebSocketEngine(WebSocket parent, DiscordWebSocketClientConfig config) { _parent = parent; - _userAgent = userAgent; - _sendInterval = sendInterval; + _config = config; _sendQueue = new ConcurrentQueue(); } @@ -35,7 +34,8 @@ namespace Discord.Net _webSocket = new WSSharpNWebSocket(host); _webSocket.EmitOnPing = false; _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.OnError += async (s, e) => { @@ -77,6 +77,7 @@ namespace Discord.Net private Task SendAsync(CancellationToken cancelToken) { + var sendInterval = _config.WebSocketInterval; return Task.Run(async () => { try @@ -86,7 +87,7 @@ namespace Discord.Net string json; while (_sendQueue.TryDequeue(out json)) _webSocket.Send(json); - await Task.Delay(_sendInterval, cancelToken).ConfigureAwait(false); + await Task.Delay(sendInterval, cancelToken).ConfigureAwait(false); } } catch (OperationCanceledException) { } diff --git a/src/Discord.Net/Net/WebSocket.cs b/src/Discord.Net/Net/WebSocket.cs index 2bb8dfc71..75bfb136c 100644 --- a/src/Discord.Net/Net/WebSocket.cs +++ b/src/Discord.Net/Net/WebSocket.cs @@ -65,7 +65,7 @@ namespace Discord.Net _cancelToken = new CancellationToken(true); _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) => { if (_logLevel >= LogMessageSeverity.Debug)