Added .Net Core support (...again)
This commit is contained in:
@@ -38,7 +38,7 @@
|
|||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<LangVersion>6</LangVersion>
|
<LangVersion>6</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'FullDebug|AnyCPU'">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'TestResponses|AnyCPU' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
<OutputPath>bin\FullDebug\</OutputPath>
|
<OutputPath>bin\FullDebug\</OutputPath>
|
||||||
<DefineConstants>TRACE;DEBUG;NET45,TEST_RESPONSES</DefineConstants>
|
<DefineConstants>TRACE;DEBUG;NET45,TEST_RESPONSES</DefineConstants>
|
||||||
@@ -515,6 +515,9 @@
|
|||||||
<Compile Include="..\Discord.Net\Net\WebSocketException.cs">
|
<Compile Include="..\Discord.Net\Net\WebSocketException.cs">
|
||||||
<Link>Net\WebSockets\WebSocketException.cs</Link>
|
<Link>Net\WebSockets\WebSocketException.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\Discord.Net\Net\WebSockets\BuiltInEngine.cs">
|
||||||
|
<Link>Net\WebSockets\BuiltInEngine.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\Discord.Net\Net\WebSockets\GatewaySocket.cs">
|
<Compile Include="..\Discord.Net\Net\WebSockets\GatewaySocket.cs">
|
||||||
<Link>Net\WebSockets\GatewaySocket.cs</Link>
|
<Link>Net\WebSockets\GatewaySocket.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|||||||
137
src/Discord.Net/Net/Rest/BuiltInEngine.cs
Normal file
137
src/Discord.Net/Net/Rest/BuiltInEngine.cs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
#if DOTNET5_4
|
||||||
|
using Discord.Logging;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Discord.Net.Rest
|
||||||
|
{
|
||||||
|
internal sealed class BuiltInEngine : IRestEngine
|
||||||
|
{
|
||||||
|
private readonly DiscordConfig _config;
|
||||||
|
private readonly HttpClient _client;
|
||||||
|
private readonly string _baseUrl;
|
||||||
|
|
||||||
|
private readonly object _rateLimitLock;
|
||||||
|
private DateTime _rateLimitTime;
|
||||||
|
|
||||||
|
internal Logger Logger { get; }
|
||||||
|
|
||||||
|
public BuiltInEngine(DiscordConfig config, string baseUrl, Logger logger)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
_baseUrl = baseUrl;
|
||||||
|
_rateLimitLock = new object();
|
||||||
|
_client = new HttpClient(new HttpClientHandler
|
||||||
|
{
|
||||||
|
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,
|
||||||
|
UseCookies = false,
|
||||||
|
UseProxy = false,
|
||||||
|
PreAuthenticate = false //We do auth ourselves
|
||||||
|
});
|
||||||
|
_client.DefaultRequestHeaders.Add("accept", "*/*");
|
||||||
|
_client.DefaultRequestHeaders.Add("accept-encoding", "gzip,deflate");
|
||||||
|
_client.DefaultRequestHeaders.Add("user-agent", config.UserAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetToken(string token)
|
||||||
|
{
|
||||||
|
_client.DefaultRequestHeaders.Remove("authorization");
|
||||||
|
if (token != null)
|
||||||
|
_client.DefaultRequestHeaders.Add("authorization", token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> Send(string method, string path, string json, CancellationToken cancelToken)
|
||||||
|
{
|
||||||
|
using (var request = new HttpRequestMessage(GetMethod(method), _baseUrl + path))
|
||||||
|
{
|
||||||
|
if (json != null)
|
||||||
|
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
|
return await Send(request, cancelToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public async Task<string> SendFile(string method, string path, string filename, Stream stream, CancellationToken cancelToken)
|
||||||
|
{
|
||||||
|
using (var request = new HttpRequestMessage(GetMethod(method), _baseUrl + path))
|
||||||
|
{
|
||||||
|
var content = new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture));
|
||||||
|
content.Add(new StreamContent(File.OpenRead(path)), "file", filename);
|
||||||
|
request.Content = content;
|
||||||
|
return await Send(request, cancelToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private async Task<string> Send(HttpRequestMessage request, CancellationToken cancelToken)
|
||||||
|
{
|
||||||
|
int retryCount = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (WebException ex)
|
||||||
|
{
|
||||||
|
//The request was aborted: Could not create SSL/TLS secure channel.
|
||||||
|
if (ex.HResult == -2146233079 && retryCount++ < 5)
|
||||||
|
continue; //Retrying seems to fix this somehow?
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
int statusCode = (int)response.StatusCode;
|
||||||
|
if (statusCode == 429) //Rate limit
|
||||||
|
{
|
||||||
|
var retryAfter = response.Headers
|
||||||
|
.Where(x => x.Key.Equals("Retry-After", StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Select(x => x.Value.FirstOrDefault())
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
int milliseconds;
|
||||||
|
if (retryAfter != null && int.TryParse(retryAfter, out milliseconds))
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
if (now >= _rateLimitTime)
|
||||||
|
{
|
||||||
|
lock (_rateLimitLock)
|
||||||
|
{
|
||||||
|
if (now >= _rateLimitTime)
|
||||||
|
{
|
||||||
|
_rateLimitTime = now.AddMilliseconds(milliseconds);
|
||||||
|
Logger.Warning($"Rate limit hit, waiting {Math.Round(milliseconds / 1000.0f, 2)} seconds");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Task.Delay(milliseconds, cancelToken).ConfigureAwait(false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw new HttpException(response.StatusCode);
|
||||||
|
}
|
||||||
|
else if (statusCode < 200 || statusCode >= 300) //2xx = Success
|
||||||
|
throw new HttpException(response.StatusCode);
|
||||||
|
else
|
||||||
|
return await response.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly HttpMethod _patch = new HttpMethod("PATCH");
|
||||||
|
private HttpMethod GetMethod(string method)
|
||||||
|
{
|
||||||
|
switch (method)
|
||||||
|
{
|
||||||
|
case "DELETE": return HttpMethod.Delete;
|
||||||
|
case "GET": return HttpMethod.Get;
|
||||||
|
case "PATCH": return _patch;
|
||||||
|
case "POST": return HttpMethod.Post;
|
||||||
|
case "PUT": return HttpMethod.Put;
|
||||||
|
default: throw new InvalidOperationException($"Unknown HttpMethod: {method}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -51,7 +51,7 @@ namespace Discord.Net.Rest
|
|||||||
#if !DOTNET5_4
|
#if !DOTNET5_4
|
||||||
_engine = new RestSharpEngine(config, baseUrl, logger);
|
_engine = new RestSharpEngine(config, baseUrl, logger);
|
||||||
#else
|
#else
|
||||||
//_engine = new BuiltInRestEngine(config, baseUrl, logger);
|
_engine = new BuiltInEngine(config, baseUrl, logger);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ namespace Discord.Net.Rest
|
|||||||
}
|
}
|
||||||
public Task<string> SendFile(string method, string path, string filename, Stream stream, CancellationToken cancelToken)
|
public Task<string> SendFile(string method, string path, string filename, Stream stream, CancellationToken cancelToken)
|
||||||
{
|
{
|
||||||
var request = new RestRequest(path, Method.POST);
|
var request = new RestRequest(path, GetMethod(method));
|
||||||
request.AddHeader("content-length", (stream.Length - stream.Position).ToString());
|
request.AddHeader("content-length", (stream.Length - stream.Position).ToString());
|
||||||
|
|
||||||
byte[] bytes = new byte[stream.Length - stream.Position];
|
byte[] bytes = new byte[stream.Length - stream.Position];
|
||||||
@@ -79,6 +79,7 @@ namespace Discord.Net.Rest
|
|||||||
{
|
{
|
||||||
var retryAfter = response.Headers
|
var retryAfter = response.Headers
|
||||||
.FirstOrDefault(x => x.Name.Equals("Retry-After", StringComparison.OrdinalIgnoreCase));
|
.FirstOrDefault(x => x.Name.Equals("Retry-After", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
int milliseconds;
|
int milliseconds;
|
||||||
if (retryAfter != null && int.TryParse((string)retryAfter.Value, out milliseconds))
|
if (retryAfter != null && int.TryParse((string)retryAfter.Value, out milliseconds))
|
||||||
{
|
{
|
||||||
|
|||||||
163
src/Discord.Net/Net/WebSockets/BuiltInEngine.cs
Normal file
163
src/Discord.Net/Net/WebSockets/BuiltInEngine.cs
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
#if DOTNET5_4
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net.WebSockets;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using WebSocketClient = System.Net.WebSockets.ClientWebSocket;
|
||||||
|
|
||||||
|
namespace Discord.Net.WebSockets
|
||||||
|
{
|
||||||
|
internal class BuiltInEngine : IWebSocketEngine
|
||||||
|
{
|
||||||
|
private const int ReceiveChunkSize = 4096;
|
||||||
|
private const int SendChunkSize = 4096;
|
||||||
|
private const int HR_TIMEOUT = -2147012894;
|
||||||
|
|
||||||
|
private readonly DiscordConfig _config;
|
||||||
|
private readonly ConcurrentQueue<string> _sendQueue;
|
||||||
|
private WebSocketClient _webSocket;
|
||||||
|
|
||||||
|
public event EventHandler<WebSocketBinaryMessageEventArgs> BinaryMessage = delegate { };
|
||||||
|
public event EventHandler<WebSocketTextMessageEventArgs> TextMessage = delegate { };
|
||||||
|
private void OnBinaryMessage(byte[] data)
|
||||||
|
=> BinaryMessage(this, new WebSocketBinaryMessageEventArgs(data));
|
||||||
|
private void OnTextMessage(string msg)
|
||||||
|
=> TextMessage(this, new WebSocketTextMessageEventArgs(msg));
|
||||||
|
|
||||||
|
internal BuiltInEngine(DiscordConfig config)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
_sendQueue = new ConcurrentQueue<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Connect(string host, CancellationToken cancelToken)
|
||||||
|
{
|
||||||
|
return Task.Run(async () =>
|
||||||
|
{
|
||||||
|
_webSocket = new WebSocketClient();
|
||||||
|
_webSocket.Options.Proxy = null;
|
||||||
|
_webSocket.Options.SetRequestHeader("User-Agent", _config.UserAgent);
|
||||||
|
_webSocket.Options.KeepAliveInterval = TimeSpan.Zero;
|
||||||
|
await _webSocket.ConnectAsync(new Uri(host), cancelToken)//.ConfigureAwait(false);
|
||||||
|
.ContinueWith(t => ReceiveAsync(cancelToken)).ConfigureAwait(false);
|
||||||
|
//TODO: ContinueWith is a temporary hack, may be a bug related to https://github.com/dotnet/corefx/issues/4429
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Disconnect()
|
||||||
|
{
|
||||||
|
string ignored;
|
||||||
|
while (_sendQueue.TryDequeue(out ignored)) { }
|
||||||
|
|
||||||
|
var socket = _webSocket;
|
||||||
|
_webSocket = null;
|
||||||
|
|
||||||
|
return TaskHelper.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Task> GetTasks(CancellationToken cancelToken)
|
||||||
|
=> new Task[] { /*ReceiveAsync(cancelToken),*/ SendAsync(cancelToken) };
|
||||||
|
|
||||||
|
private Task ReceiveAsync(CancellationToken cancelToken)
|
||||||
|
{
|
||||||
|
return Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var sendInterval = _config.WebSocketInterval;
|
||||||
|
//var buffer = new ArraySegment<byte>(new byte[ReceiveChunkSize]);
|
||||||
|
var buffer = new byte[ReceiveChunkSize];
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!cancelToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
WebSocketReceiveResult result = null;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (cancelToken.IsCancellationRequested) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancelToken);//.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT)
|
||||||
|
{
|
||||||
|
throw new Exception($"Connection timed out.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.MessageType == WebSocketMessageType.Close)
|
||||||
|
throw new WebSocketException((int)result.CloseStatus.Value, result.CloseStatusDescription);
|
||||||
|
else
|
||||||
|
stream.Write(buffer, 0, result.Count);
|
||||||
|
|
||||||
|
}
|
||||||
|
while (result == null || !result.EndOfMessage);
|
||||||
|
|
||||||
|
var array = stream.ToArray();
|
||||||
|
if (result.MessageType == WebSocketMessageType.Binary)
|
||||||
|
OnBinaryMessage(array);
|
||||||
|
else if (result.MessageType == WebSocketMessageType.Text)
|
||||||
|
OnTextMessage(Encoding.UTF8.GetString(array, 0, array.Length));
|
||||||
|
|
||||||
|
stream.Position = 0;
|
||||||
|
stream.SetLength(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private Task SendAsync(CancellationToken cancelToken)
|
||||||
|
{
|
||||||
|
return Task.Run(async () =>
|
||||||
|
{
|
||||||
|
byte[] bytes = new byte[SendChunkSize];
|
||||||
|
var sendInterval = _config.WebSocketInterval;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!cancelToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
string json;
|
||||||
|
while (_sendQueue.TryDequeue(out json))
|
||||||
|
{
|
||||||
|
int byteCount = Encoding.UTF8.GetBytes(json, 0, json.Length, bytes, 0);
|
||||||
|
int frameCount = (int)Math.Ceiling((double)byteCount / SendChunkSize);
|
||||||
|
|
||||||
|
int offset = 0;
|
||||||
|
for (var i = 0; i < frameCount; i++, offset += SendChunkSize)
|
||||||
|
{
|
||||||
|
bool isLast = i == (frameCount - 1);
|
||||||
|
|
||||||
|
int count;
|
||||||
|
if (isLast)
|
||||||
|
count = byteCount - (i * SendChunkSize);
|
||||||
|
else
|
||||||
|
count = SendChunkSize;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _webSocket.SendAsync(new ArraySegment<byte>(bytes, offset, count), WebSocketMessageType.Text, isLast, cancelToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Task.Delay(sendInterval, cancelToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void QueueMessage(string message)
|
||||||
|
=> _sendQueue.Enqueue(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using WebSocket4Net;
|
using WebSocket4Net;
|
||||||
using WS4NetWebSocket = WebSocket4Net.WebSocket;
|
using WebSocketClient = WebSocket4Net.WebSocket;
|
||||||
|
|
||||||
namespace Discord.Net.WebSockets
|
namespace Discord.Net.WebSockets
|
||||||
{
|
{
|
||||||
@@ -15,7 +15,7 @@ namespace Discord.Net.WebSockets
|
|||||||
private readonly DiscordConfig _config;
|
private readonly DiscordConfig _config;
|
||||||
private readonly ConcurrentQueue<string> _sendQueue;
|
private readonly ConcurrentQueue<string> _sendQueue;
|
||||||
private readonly TaskManager _taskManager;
|
private readonly TaskManager _taskManager;
|
||||||
private WS4NetWebSocket _webSocket;
|
private WebSocketClient _webSocket;
|
||||||
private ManualResetEventSlim _waitUntilConnect;
|
private ManualResetEventSlim _waitUntilConnect;
|
||||||
|
|
||||||
public event EventHandler<WebSocketBinaryMessageEventArgs> BinaryMessage = delegate { };
|
public event EventHandler<WebSocketBinaryMessageEventArgs> BinaryMessage = delegate { };
|
||||||
@@ -35,7 +35,7 @@ namespace Discord.Net.WebSockets
|
|||||||
|
|
||||||
public Task Connect(string host, CancellationToken cancelToken)
|
public Task Connect(string host, CancellationToken cancelToken)
|
||||||
{
|
{
|
||||||
_webSocket = new WS4NetWebSocket(host);
|
_webSocket = new WebSocketClient(host);
|
||||||
_webSocket.EnableAutoSendPing = false;
|
_webSocket.EnableAutoSendPing = false;
|
||||||
_webSocket.NoDelay = true;
|
_webSocket.NoDelay = true;
|
||||||
_webSocket.Proxy = null;
|
_webSocket.Proxy = null;
|
||||||
@@ -96,7 +96,8 @@ namespace Discord.Net.WebSockets
|
|||||||
private void OnWebSocketBinary(object sender, DataReceivedEventArgs e)
|
private void OnWebSocketBinary(object sender, DataReceivedEventArgs e)
|
||||||
=> OnBinaryMessage(e.Data);
|
=> OnBinaryMessage(e.Data);
|
||||||
|
|
||||||
public IEnumerable<Task> GetTasks(CancellationToken cancelToken) => new Task[] { SendAsync(cancelToken) };
|
public IEnumerable<Task> GetTasks(CancellationToken cancelToken)
|
||||||
|
=> new Task[] { SendAsync(cancelToken) };
|
||||||
|
|
||||||
private Task SendAsync(CancellationToken cancelToken)
|
private Task SendAsync(CancellationToken cancelToken)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ namespace Discord.Net.WebSockets
|
|||||||
#if !DOTNET5_4
|
#if !DOTNET5_4
|
||||||
_engine = new WS4NetEngine(client.Config, _taskManager);
|
_engine = new WS4NetEngine(client.Config, _taskManager);
|
||||||
#else
|
#else
|
||||||
//_engine = new BuiltInWebSocketEngine(this, client.Config);
|
_engine = new BuiltInEngine(client.Config);
|
||||||
#endif
|
#endif
|
||||||
_engine.BinaryMessage += (s, e) =>
|
_engine.BinaryMessage += (s, e) =>
|
||||||
{
|
{
|
||||||
@@ -179,7 +179,8 @@ namespace Discord.Net.WebSockets
|
|||||||
{
|
{
|
||||||
//Cancel if either DiscordClient.Disconnect is called, data socket errors or timeout is reached
|
//Cancel if either DiscordClient.Disconnect is called, data socket errors or timeout is reached
|
||||||
cancelToken = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, CancelToken).Token;
|
cancelToken = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, CancelToken).Token;
|
||||||
_connectedEvent.Wait(cancelToken);
|
if (!_connectedEvent.Wait(_client.Config.ConnectionTimeout, cancelToken))
|
||||||
|
throw new TimeoutException();
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,12 +33,6 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"net45": {
|
|
||||||
"dependencies": {
|
|
||||||
"WebSocket4Net": "0.14.1",
|
|
||||||
"RestSharp": "105.2.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dotnet5.4": {
|
"dotnet5.4": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"System.Collections": "4.0.11-beta-23516",
|
"System.Collections": "4.0.11-beta-23516",
|
||||||
@@ -47,6 +41,7 @@
|
|||||||
"System.IO.FileSystem": "4.0.1-beta-23516",
|
"System.IO.FileSystem": "4.0.1-beta-23516",
|
||||||
"System.IO.Compression": "4.1.0-beta-23516",
|
"System.IO.Compression": "4.1.0-beta-23516",
|
||||||
"System.Linq": "4.0.1-beta-23516",
|
"System.Linq": "4.0.1-beta-23516",
|
||||||
|
"System.Net.Http": "4.0.1-beta-23516",
|
||||||
"System.Net.NameResolution": "4.0.0-beta-23516",
|
"System.Net.NameResolution": "4.0.0-beta-23516",
|
||||||
"System.Net.Sockets": "4.1.0-beta-23409",
|
"System.Net.Sockets": "4.1.0-beta-23409",
|
||||||
"System.Net.Requests": "4.0.11-beta-23516",
|
"System.Net.Requests": "4.0.11-beta-23516",
|
||||||
@@ -57,6 +52,12 @@
|
|||||||
"System.Threading": "4.0.11-beta-23516",
|
"System.Threading": "4.0.11-beta-23516",
|
||||||
"System.Threading.Thread": "4.0.0-beta-23516"
|
"System.Threading.Thread": "4.0.0-beta-23516"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"net45": {
|
||||||
|
"dependencies": {
|
||||||
|
"WebSocket4Net": "0.14.1",
|
||||||
|
"RestSharp": "105.2.3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user