Initial RPC commit, Added Authorize and Authenticate
This commit is contained in:
@@ -106,7 +106,7 @@ namespace Discord.API
|
||||
}
|
||||
}
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
|
||||
public async Task LoginAsync(TokenType tokenType, string token, RequestOptions options = null)
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
@@ -121,7 +121,7 @@ namespace Discord.API
|
||||
if (LoginState != LoginState.LoggedOut)
|
||||
await LogoutInternalAsync().ConfigureAwait(false);
|
||||
LoginState = LoginState.LoggingIn;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
_loginCancelToken = new CancellationTokenSource();
|
||||
@@ -172,7 +172,7 @@ namespace Discord.API
|
||||
//An exception here will lock the client into the unusable LoggingOut state, but that's probably fine since our client is in an undefined state too.
|
||||
if (LoginState == LoginState.LoggedOut) return;
|
||||
LoginState = LoginState.LoggingOut;
|
||||
|
||||
|
||||
try { _loginCancelToken?.Cancel(false); }
|
||||
catch { }
|
||||
|
||||
@@ -250,7 +250,7 @@ namespace Discord.API
|
||||
|
||||
if (ConnectionState == ConnectionState.Disconnected) return;
|
||||
ConnectionState = ConnectionState.Disconnecting;
|
||||
|
||||
|
||||
try { _connectCancelToken?.Cancel(false); }
|
||||
catch { }
|
||||
|
||||
@@ -260,29 +260,29 @@ namespace Discord.API
|
||||
}
|
||||
|
||||
//REST
|
||||
public Task SendAsync(string method, string endpoint,
|
||||
public Task SendAsync(string method, string endpoint,
|
||||
GlobalBucket bucket = GlobalBucket.GeneralRest, RequestOptions options = null)
|
||||
=> SendInternalAsync(method, endpoint, null, true, BucketGroup.Global, (int)bucket, 0, options);
|
||||
public Task SendAsync(string method, string endpoint, object payload,
|
||||
public Task SendAsync(string method, string endpoint, object payload,
|
||||
GlobalBucket bucket = GlobalBucket.GeneralRest, RequestOptions options = null)
|
||||
=> SendInternalAsync(method, endpoint, payload, true, BucketGroup.Global, (int)bucket, 0, options);
|
||||
public async Task<TResponse> SendAsync<TResponse>(string method, string endpoint,
|
||||
GlobalBucket bucket = GlobalBucket.GeneralRest, RequestOptions options = null) where TResponse : class
|
||||
=> DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, null, false, BucketGroup.Global, (int)bucket, 0, options).ConfigureAwait(false));
|
||||
public async Task<TResponse> SendAsync<TResponse>(string method, string endpoint, object payload, GlobalBucket bucket =
|
||||
public async Task<TResponse> SendAsync<TResponse>(string method, string endpoint, object payload, GlobalBucket bucket =
|
||||
GlobalBucket.GeneralRest, RequestOptions options = null) where TResponse : class
|
||||
=> DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, payload, false, BucketGroup.Global, (int)bucket, 0, options).ConfigureAwait(false));
|
||||
|
||||
public Task SendAsync(string method, string endpoint,
|
||||
|
||||
public Task SendAsync(string method, string endpoint,
|
||||
GuildBucket bucket, ulong guildId, RequestOptions options = null)
|
||||
=> SendInternalAsync(method, endpoint, null, true, BucketGroup.Guild, (int)bucket, guildId, options);
|
||||
public Task SendAsync(string method, string endpoint, object payload,
|
||||
public Task SendAsync(string method, string endpoint, object payload,
|
||||
GuildBucket bucket, ulong guildId, RequestOptions options = null)
|
||||
=> SendInternalAsync(method, endpoint, payload, true, BucketGroup.Guild, (int)bucket, guildId, options);
|
||||
public async Task<TResponse> SendAsync<TResponse>(string method, string endpoint,
|
||||
public async Task<TResponse> SendAsync<TResponse>(string method, string endpoint,
|
||||
GuildBucket bucket, ulong guildId, RequestOptions options = null) where TResponse : class
|
||||
=> DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, null, false, BucketGroup.Guild, (int)bucket, guildId, options).ConfigureAwait(false));
|
||||
public async Task<TResponse> SendAsync<TResponse>(string method, string endpoint, object payload,
|
||||
public async Task<TResponse> SendAsync<TResponse>(string method, string endpoint, object payload,
|
||||
GuildBucket bucket, ulong guildId, RequestOptions options = null) where TResponse : class
|
||||
=> DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, payload, false, BucketGroup.Guild, (int)bucket, guildId, options).ConfigureAwait(false));
|
||||
|
||||
@@ -311,7 +311,7 @@ namespace Discord.API
|
||||
=> SendGatewayInternalAsync(opCode, payload, BucketGroup.Guild, (int)bucket, guildId, options);
|
||||
|
||||
//Core
|
||||
private async Task<Stream> SendInternalAsync(string method, string endpoint, object payload, bool headerOnly,
|
||||
private async Task<Stream> SendInternalAsync(string method, string endpoint, object payload, bool headerOnly,
|
||||
BucketGroup group, int bucketId, ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
@@ -326,7 +326,7 @@ namespace Discord.API
|
||||
|
||||
return responseStream;
|
||||
}
|
||||
private async Task<Stream> SendMultipartInternalAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs, bool headerOnly,
|
||||
private async Task<Stream> SendMultipartInternalAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartArgs, bool headerOnly,
|
||||
BucketGroup group, int bucketId, ulong guildId, RequestOptions options = null)
|
||||
{
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
@@ -339,7 +339,7 @@ namespace Discord.API
|
||||
|
||||
return responseStream;
|
||||
}
|
||||
private async Task SendGatewayInternalAsync(GatewayOpCode opCode, object payload,
|
||||
private async Task SendGatewayInternalAsync(GatewayOpCode opCode, object payload,
|
||||
BucketGroup group, int bucketId, ulong guildId, RequestOptions options)
|
||||
{
|
||||
//TODO: Add ETF
|
||||
@@ -913,7 +913,7 @@ namespace Discord.API
|
||||
relativeDir = "around";
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
int runs = (limit + DiscordRestConfig.MaxMessagesPerBatch - 1) / DiscordRestConfig.MaxMessagesPerBatch;
|
||||
int lastRunCount = limit - (runs - 1) * DiscordRestConfig.MaxMessagesPerBatch;
|
||||
var result = new API.Message[runs][];
|
||||
@@ -1027,7 +1027,7 @@ namespace Discord.API
|
||||
{
|
||||
Preconditions.NotNull(args, nameof(args));
|
||||
Preconditions.NotEqual(channelId, 0, nameof(channelId));
|
||||
|
||||
|
||||
if (args._content.GetValueOrDefault(null) == null)
|
||||
args._content = "";
|
||||
else if (args._content.IsSpecified)
|
||||
@@ -1153,7 +1153,7 @@ namespace Discord.API
|
||||
{
|
||||
Preconditions.NotNullOrEmpty(username, nameof(username));
|
||||
Preconditions.NotNullOrEmpty(discriminator, nameof(discriminator));
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var models = await QueryUsersAsync($"{username}#{discriminator}", 1, options: options).ConfigureAwait(false);
|
||||
|
||||
288
src/Discord.Net/API/DiscordRpcAPIClient.cs
Normal file
288
src/Discord.Net/API/DiscordRpcAPIClient.cs
Normal file
@@ -0,0 +1,288 @@
|
||||
using Discord.API.Gateway;
|
||||
using Discord.API.Rest;
|
||||
using Discord.API.Rpc;
|
||||
using Discord.Net;
|
||||
using Discord.Net.Converters;
|
||||
using Discord.Net.Queue;
|
||||
using Discord.Net.Rest;
|
||||
using Discord.Net.WebSockets;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord.API
|
||||
{
|
||||
public class DiscordRpcApiClient : IDisposable
|
||||
{
|
||||
private object _eventLock = new object();
|
||||
|
||||
public event Func<string, Task> SentRpcMessage { add { _sentRpcMessageEvent.Add(value); } remove { _sentRpcMessageEvent.Remove(value); } }
|
||||
private readonly AsyncEvent<Func<string, Task>> _sentRpcMessageEvent = new AsyncEvent<Func<string, Task>>();
|
||||
|
||||
public event Func<string, string, object, string, Task> ReceivedRpcEvent { add { _receivedRpcEvent.Add(value); } remove { _receivedRpcEvent.Remove(value); } }
|
||||
private readonly AsyncEvent<Func<string, string, object, string, Task>> _receivedRpcEvent = new AsyncEvent<Func<string, string, object, string, Task>>();
|
||||
public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } }
|
||||
private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>();
|
||||
|
||||
private readonly RequestQueue _requestQueue;
|
||||
private readonly JsonSerializer _serializer;
|
||||
private readonly IWebSocketClient _webSocketClient;
|
||||
private readonly SemaphoreSlim _connectionLock;
|
||||
private readonly string _clientId;
|
||||
private CancellationTokenSource _loginCancelToken, _connectCancelToken;
|
||||
private string _authToken;
|
||||
private bool _isDisposed;
|
||||
|
||||
public LoginState LoginState { get; private set; }
|
||||
public ConnectionState ConnectionState { get; private set; }
|
||||
|
||||
public DiscordRpcApiClient(string clientId, WebSocketProvider webSocketProvider, JsonSerializer serializer = null, RequestQueue requestQueue = null)
|
||||
{
|
||||
_connectionLock = new SemaphoreSlim(1, 1);
|
||||
_clientId = clientId;
|
||||
|
||||
_requestQueue = requestQueue ?? new RequestQueue();
|
||||
|
||||
if (webSocketProvider != null)
|
||||
{
|
||||
_webSocketClient = webSocketProvider();
|
||||
//_gatewayClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .Net 4.6+)
|
||||
_webSocketClient.BinaryMessage += async (data, index, count) =>
|
||||
{
|
||||
using (var compressed = new MemoryStream(data, index + 2, count - 2))
|
||||
using (var decompressed = new MemoryStream())
|
||||
{
|
||||
using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress))
|
||||
zlib.CopyTo(decompressed);
|
||||
decompressed.Position = 0;
|
||||
using (var reader = new StreamReader(decompressed))
|
||||
{
|
||||
var msg = JsonConvert.DeserializeObject<RpcMessage>(reader.ReadToEnd());
|
||||
await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data, msg.Nonce).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
_webSocketClient.TextMessage += async text =>
|
||||
{
|
||||
var msg = JsonConvert.DeserializeObject<RpcMessage>(text);
|
||||
await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data, msg.Nonce).ConfigureAwait(false);
|
||||
};
|
||||
_webSocketClient.Closed += async ex =>
|
||||
{
|
||||
await DisconnectAsync().ConfigureAwait(false);
|
||||
await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false);
|
||||
};
|
||||
}
|
||||
|
||||
_serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() };
|
||||
}
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_connectCancelToken?.Dispose();
|
||||
(_webSocketClient as IDisposable)?.Dispose();
|
||||
}
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
public async Task LoginAsync(TokenType tokenType, string token, RequestOptions options = null)
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await LoginInternalAsync(tokenType, token, options).ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
private async Task LoginInternalAsync(TokenType tokenType, string token, RequestOptions options = null)
|
||||
{
|
||||
if (LoginState != LoginState.LoggedOut)
|
||||
await LogoutInternalAsync().ConfigureAwait(false);
|
||||
|
||||
if (tokenType != TokenType.Bearer)
|
||||
throw new InvalidOperationException("RPC only supports bearer tokens");
|
||||
|
||||
LoginState = LoginState.LoggingIn;
|
||||
try
|
||||
{
|
||||
_loginCancelToken = new CancellationTokenSource();
|
||||
await _requestQueue.SetCancelTokenAsync(_loginCancelToken.Token).ConfigureAwait(false);
|
||||
|
||||
_authToken = token;
|
||||
|
||||
LoginState = LoginState.LoggedIn;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await LogoutInternalAsync().ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LogoutAsync()
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await LogoutInternalAsync().ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
private async Task LogoutInternalAsync()
|
||||
{
|
||||
//An exception here will lock the client into the unusable LoggingOut state, but that's probably fine since our client is in an undefined state too.
|
||||
if (LoginState == LoginState.LoggedOut) return;
|
||||
LoginState = LoginState.LoggingOut;
|
||||
|
||||
try { _loginCancelToken?.Cancel(false); }
|
||||
catch { }
|
||||
|
||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
||||
await _requestQueue.ClearAsync().ConfigureAwait(false);
|
||||
|
||||
await _requestQueue.SetCancelTokenAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
LoginState = LoginState.LoggedOut;
|
||||
}
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await ConnectInternalAsync().ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
private async Task ConnectInternalAsync()
|
||||
{
|
||||
/*if (LoginState != LoginState.LoggedIn)
|
||||
throw new InvalidOperationException("You must log in before connecting.");*/
|
||||
|
||||
ConnectionState = ConnectionState.Connecting;
|
||||
try
|
||||
{
|
||||
_connectCancelToken = new CancellationTokenSource();
|
||||
if (_webSocketClient != null)
|
||||
_webSocketClient.SetCancelToken(_connectCancelToken.Token);
|
||||
|
||||
bool success = false;
|
||||
for (int port = DiscordRpcConfig.PortRangeStart; port <= DiscordRpcConfig.PortRangeEnd; port++)
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = $"wss://discordapp.io:{port}/?v={DiscordRpcConfig.RpcAPIVersion}&client_id={_clientId}";
|
||||
await _webSocketClient.ConnectAsync(url).ConfigureAwait(false);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
if (!success)
|
||||
throw new Exception("Unable to connect to the RPC server.");
|
||||
|
||||
ConnectionState = ConnectionState.Connected;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
private async Task DisconnectInternalAsync()
|
||||
{
|
||||
if (_webSocketClient == null)
|
||||
throw new NotSupportedException("This client is not configured with websocket support.");
|
||||
|
||||
if (ConnectionState == ConnectionState.Disconnected) return;
|
||||
ConnectionState = ConnectionState.Disconnecting;
|
||||
|
||||
try { _connectCancelToken?.Cancel(false); }
|
||||
catch { }
|
||||
|
||||
await _webSocketClient.DisconnectAsync().ConfigureAwait(false);
|
||||
|
||||
ConnectionState = ConnectionState.Disconnected;
|
||||
}
|
||||
|
||||
//Core
|
||||
public Task SendRpcAsync(string cmd, object payload, GlobalBucket bucket = GlobalBucket.GeneralRpc, RequestOptions options = null)
|
||||
=> SendRpcAsyncInternal(cmd, payload, BucketGroup.Global, (int)bucket, 0, options);
|
||||
public Task SendRpcAsync(string cmd, object payload, GuildBucket bucket, ulong guildId, RequestOptions options = null)
|
||||
=> SendRpcAsyncInternal(cmd, payload, BucketGroup.Guild, (int)bucket, guildId, options);
|
||||
private async Task SendRpcAsyncInternal(string cmd, object payload,
|
||||
BucketGroup group, int bucketId, ulong guildId, RequestOptions options)
|
||||
{
|
||||
//TODO: Add Nonce to pair sent requests with responses
|
||||
byte[] bytes = null;
|
||||
payload = new RpcMessage { Cmd = cmd, Args = payload, Nonce = Guid.NewGuid().ToString() };
|
||||
if (payload != null)
|
||||
bytes = Encoding.UTF8.GetBytes(SerializeJson(payload));
|
||||
await _requestQueue.SendAsync(new WebSocketRequest(_webSocketClient, bytes, true, options), group, bucketId, guildId).ConfigureAwait(false);
|
||||
await _sentRpcMessageEvent.InvokeAsync(cmd).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Rpc
|
||||
public async Task SendAuthenticateAsync(RequestOptions options = null)
|
||||
{
|
||||
var msg = new AuthenticateParams()
|
||||
{
|
||||
AccessToken = _authToken
|
||||
};
|
||||
await SendRpcAsync("AUTHENTICATE", msg, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task SendAuthorizeAsync(string[] scopes, RequestOptions options = null)
|
||||
{
|
||||
var msg = new AuthorizeParams()
|
||||
{
|
||||
ClientId = _clientId,
|
||||
Scopes = scopes
|
||||
};
|
||||
await SendRpcAsync("AUTHORIZE", msg, options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Helpers
|
||||
private static double ToMilliseconds(Stopwatch stopwatch) => Math.Round((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2);
|
||||
private string SerializeJson(object value)
|
||||
{
|
||||
var sb = new StringBuilder(256);
|
||||
using (TextWriter text = new StringWriter(sb, CultureInfo.InvariantCulture))
|
||||
using (JsonWriter writer = new JsonTextWriter(text))
|
||||
_serializer.Serialize(writer, value);
|
||||
return sb.ToString();
|
||||
}
|
||||
private T DeserializeJson<T>(Stream jsonStream)
|
||||
{
|
||||
using (TextReader text = new StreamReader(jsonStream))
|
||||
using (JsonReader reader = new JsonTextReader(text))
|
||||
return _serializer.Deserialize<T>(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/Discord.Net/API/Rpc/Application.cs
Normal file
18
src/Discord.Net/API/Rpc/Application.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
public class Application
|
||||
{
|
||||
[JsonProperty("description")]
|
||||
public string Description { get; set; }
|
||||
[JsonProperty("icon")]
|
||||
public string Icon { get; set; }
|
||||
[JsonProperty("id")]
|
||||
public ulong Id { get; set; }
|
||||
[JsonProperty("rpc_origins")]
|
||||
public string RpcOrigins { get; set; }
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
17
src/Discord.Net/API/Rpc/AuthenticateEvent.cs
Normal file
17
src/Discord.Net/API/Rpc/AuthenticateEvent.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
public class AuthenticateEvent
|
||||
{
|
||||
[JsonProperty("application")]
|
||||
public Application Application { get; set; }
|
||||
[JsonProperty("expires")]
|
||||
public DateTimeOffset Expires { get; set; }
|
||||
[JsonProperty("user")]
|
||||
public User User { get; set; }
|
||||
[JsonProperty("scopes")]
|
||||
public string[] Scopes { get; set; }
|
||||
}
|
||||
}
|
||||
10
src/Discord.Net/API/Rpc/AuthenticateParams.cs
Normal file
10
src/Discord.Net/API/Rpc/AuthenticateParams.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
public class AuthenticateParams
|
||||
{
|
||||
[JsonProperty("access_token")]
|
||||
public string AccessToken { get; set; }
|
||||
}
|
||||
}
|
||||
11
src/Discord.Net/API/Rpc/AuthorizeEvent.cs
Normal file
11
src/Discord.Net/API/Rpc/AuthorizeEvent.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
public class AuthorizeEvent
|
||||
{
|
||||
[JsonProperty("code")]
|
||||
public string Code { get; set; }
|
||||
}
|
||||
}
|
||||
12
src/Discord.Net/API/Rpc/AuthorizeParams.cs
Normal file
12
src/Discord.Net/API/Rpc/AuthorizeParams.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
public class AuthorizeParams
|
||||
{
|
||||
[JsonProperty("client_id")]
|
||||
public string ClientId { get; set; }
|
||||
[JsonProperty("scopes")]
|
||||
public string[] Scopes { get; set; }
|
||||
}
|
||||
}
|
||||
12
src/Discord.Net/API/Rpc/ErrorEvent.cs
Normal file
12
src/Discord.Net/API/Rpc/ErrorEvent.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
public class ErrorEvent
|
||||
{
|
||||
[JsonProperty("code")]
|
||||
public int Code { get; set; }
|
||||
[JsonProperty("message")]
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
12
src/Discord.Net/API/Rpc/GuildStatusEvent.cs
Normal file
12
src/Discord.Net/API/Rpc/GuildStatusEvent.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
public class GuildStatusEvent
|
||||
{
|
||||
[JsonProperty("guild")]
|
||||
public Guild Guild { get; set; }
|
||||
[JsonProperty("online")]
|
||||
public int Online { get; set; }
|
||||
}
|
||||
}
|
||||
12
src/Discord.Net/API/Rpc/ReadyEvent.cs
Normal file
12
src/Discord.Net/API/Rpc/ReadyEvent.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
public class ReadyEvent
|
||||
{
|
||||
[JsonProperty("v")]
|
||||
public int Version { get; set; }
|
||||
[JsonProperty("config")]
|
||||
public RpcConfig Config { get; set; }
|
||||
}
|
||||
}
|
||||
14
src/Discord.Net/API/Rpc/RpcConfig.cs
Normal file
14
src/Discord.Net/API/Rpc/RpcConfig.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
public class RpcConfig
|
||||
{
|
||||
[JsonProperty("cdn_host")]
|
||||
public string CdnHost { get; set; }
|
||||
[JsonProperty("api_endpoint")]
|
||||
public string ApiEndpoint { get; set; }
|
||||
[JsonProperty("environment")]
|
||||
public string Environment { get; set; }
|
||||
}
|
||||
}
|
||||
18
src/Discord.Net/API/Rpc/RpcMessage.cs
Normal file
18
src/Discord.Net/API/Rpc/RpcMessage.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Discord.API.Rpc
|
||||
{
|
||||
public class RpcMessage
|
||||
{
|
||||
[JsonProperty("cmd")]
|
||||
public string Cmd { get; set; }
|
||||
[JsonProperty("nonce")]
|
||||
public string Nonce { get; set; }
|
||||
[JsonProperty("evt")]
|
||||
public string Event { get; set; }
|
||||
[JsonProperty("data")]
|
||||
public object Data { get; set; }
|
||||
[JsonProperty("args")]
|
||||
public object Args { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,9 @@ namespace Discord
|
||||
internal readonly ILogger _clientLogger, _restLogger, _queueLogger;
|
||||
internal readonly SemaphoreSlim _connectionLock;
|
||||
internal readonly RequestQueue _requestQueue;
|
||||
internal bool _isDisposed;
|
||||
internal SelfUser _currentUser;
|
||||
private bool _isFirstLogSub;
|
||||
internal bool _isDisposed;
|
||||
|
||||
public API.DiscordApiClient ApiClient { get; }
|
||||
internal LogManager LogManager { get; }
|
||||
@@ -303,8 +303,10 @@ namespace Discord
|
||||
internal virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
ApiClient.Dispose();
|
||||
_isDisposed = true;
|
||||
ApiClient.Dispose();
|
||||
}
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => Dispose(true);
|
||||
@@ -312,10 +314,11 @@ namespace Discord
|
||||
private async Task WriteInitialLog()
|
||||
{
|
||||
if (this is DiscordSocketClient)
|
||||
await _clientLogger.InfoAsync($"DiscordSocketClient v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion}, {DiscordSocketConfig.GatewayEncoding})").ConfigureAwait(false);
|
||||
await _clientLogger.InfoAsync($"DiscordSocketClient v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion}, {DiscordConfig.GatewayEncoding})").ConfigureAwait(false);
|
||||
else if (this is DiscordRpcClient)
|
||||
await _clientLogger.InfoAsync($"DiscordRpcClient v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion}, RPC API v{DiscordRpcConfig.RpcAPIVersion})").ConfigureAwait(false);
|
||||
else
|
||||
await _clientLogger.InfoAsync($"DiscordRestClient v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion})").ConfigureAwait(false);
|
||||
|
||||
await _clientLogger.InfoAsync($"DiscordClient v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion})").ConfigureAwait(false);
|
||||
await _clientLogger.VerboseAsync($"Runtime: {RuntimeInformation.FrameworkDescription.Trim()} ({ToArchString(RuntimeInformation.ProcessArchitecture)})").ConfigureAwait(false);
|
||||
await _clientLogger.VerboseAsync($"OS: {RuntimeInformation.OSDescription.Trim()} ({ToArchString(RuntimeInformation.OSArchitecture)})").ConfigureAwait(false);
|
||||
await _clientLogger.VerboseAsync($"Processors: {Environment.ProcessorCount}").ConfigureAwait(false);
|
||||
|
||||
379
src/Discord.Net/DiscordRpcClient.cs
Normal file
379
src/Discord.Net/DiscordRpcClient.cs
Normal file
@@ -0,0 +1,379 @@
|
||||
using Discord.API.Rpc;
|
||||
using Discord.Logging;
|
||||
using Discord.Net.Converters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public class DiscordRpcClient
|
||||
{
|
||||
public event Func<LogMessage, Task> Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } }
|
||||
private readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>();
|
||||
|
||||
public event Func<Task> LoggedIn { add { _loggedInEvent.Add(value); } remove { _loggedInEvent.Remove(value); } }
|
||||
private readonly AsyncEvent<Func<Task>> _loggedInEvent = new AsyncEvent<Func<Task>>();
|
||||
public event Func<Task> LoggedOut { add { _loggedOutEvent.Add(value); } remove { _loggedOutEvent.Remove(value); } }
|
||||
private readonly AsyncEvent<Func<Task>> _loggedOutEvent = new AsyncEvent<Func<Task>>();
|
||||
|
||||
public event Func<Task> Connected { add { _connectedEvent.Add(value); } remove { _connectedEvent.Remove(value); } }
|
||||
private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>();
|
||||
public event Func<Exception, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } }
|
||||
private readonly AsyncEvent<Func<Exception, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, Task>>();
|
||||
|
||||
public event Func<Task> Ready { add { _readyEvent.Add(value); } remove { _readyEvent.Remove(value); } }
|
||||
private readonly AsyncEvent<Func<Task>> _readyEvent = new AsyncEvent<Func<Task>>();
|
||||
|
||||
private readonly ILogger _clientLogger, _rpcLogger;
|
||||
private readonly SemaphoreSlim _connectionLock;
|
||||
private readonly JsonSerializer _serializer;
|
||||
|
||||
private TaskCompletionSource<bool> _connectTask;
|
||||
private CancellationTokenSource _cancelToken;
|
||||
internal SelfUser _currentUser;
|
||||
private Task _reconnectTask;
|
||||
private bool _isFirstLogSub;
|
||||
private bool _isReconnecting;
|
||||
private bool _isDisposed;
|
||||
private string[] _scopes;
|
||||
|
||||
public API.DiscordRpcApiClient ApiClient { get; }
|
||||
internal LogManager LogManager { get; }
|
||||
public LoginState LoginState { get; private set; }
|
||||
public ConnectionState ConnectionState { get; private set; }
|
||||
|
||||
/// <summary> Creates a new RPC discord client. </summary>
|
||||
public DiscordRpcClient(string clientId) : this(new DiscordRpcConfig(clientId)) { }
|
||||
/// <summary> Creates a new RPC discord client. </summary>
|
||||
public DiscordRpcClient(DiscordRpcConfig config)
|
||||
{
|
||||
LogManager = new LogManager(config.LogLevel);
|
||||
LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false);
|
||||
_clientLogger = LogManager.CreateLogger("Client");
|
||||
_rpcLogger = LogManager.CreateLogger("RPC");
|
||||
_isFirstLogSub = true;
|
||||
|
||||
_connectionLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
_serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };
|
||||
_serializer.Error += (s, e) =>
|
||||
{
|
||||
_rpcLogger.WarningAsync(e.ErrorContext.Error).GetAwaiter().GetResult();
|
||||
e.ErrorContext.Handled = true;
|
||||
};
|
||||
|
||||
ApiClient = new API.DiscordRpcApiClient(config.ClientId, config.WebSocketProvider);
|
||||
ApiClient.SentRpcMessage += async opCode => await _rpcLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false);
|
||||
ApiClient.ReceivedRpcEvent += ProcessMessageAsync;
|
||||
ApiClient.Disconnected += async ex =>
|
||||
{
|
||||
if (ex != null)
|
||||
{
|
||||
await _rpcLogger.WarningAsync($"Connection Closed: {ex.Message}").ConfigureAwait(false);
|
||||
await StartReconnectAsync(ex).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await _rpcLogger.WarningAsync($"Connection Closed").ConfigureAwait(false);
|
||||
};
|
||||
}
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
ApiClient.Dispose();
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task LoginAsync(TokenType tokenType, string token, bool validateToken = true)
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await LoginInternalAsync(tokenType, token, validateToken).ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
private async Task LoginInternalAsync(TokenType tokenType, string token, bool validateToken)
|
||||
{
|
||||
if (_isFirstLogSub)
|
||||
{
|
||||
_isFirstLogSub = false;
|
||||
await WriteInitialLog().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (LoginState != LoginState.LoggedOut)
|
||||
await LogoutInternalAsync().ConfigureAwait(false);
|
||||
LoginState = LoginState.LoggingIn;
|
||||
|
||||
try
|
||||
{
|
||||
await ApiClient.LoginAsync(tokenType, token).ConfigureAwait(false);
|
||||
|
||||
LoginState = LoginState.LoggedIn;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await LogoutInternalAsync().ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
|
||||
await _loggedInEvent.InvokeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task LogoutAsync()
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await LogoutInternalAsync().ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
private async Task LogoutInternalAsync()
|
||||
{
|
||||
if (LoginState == LoginState.LoggedOut) return;
|
||||
LoginState = LoginState.LoggingOut;
|
||||
|
||||
await ApiClient.LogoutAsync().ConfigureAwait(false);
|
||||
|
||||
_currentUser = null;
|
||||
|
||||
LoginState = LoginState.LoggedOut;
|
||||
|
||||
await _loggedOutEvent.InvokeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
_isReconnecting = false;
|
||||
await ConnectInternalAsync(null).ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
public async Task ConnectAndAuthorizeAsync(params string[] scopes)
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
_isReconnecting = false;
|
||||
await ConnectInternalAsync(scopes).ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
private async Task ConnectInternalAsync(string[] scopes)
|
||||
{
|
||||
if (scopes == null && LoginState != LoginState.LoggedIn)
|
||||
throw new InvalidOperationException("You must log in before connecting or call ConnectAndAuthorizeAsync.");
|
||||
_scopes = scopes;
|
||||
|
||||
if (_isFirstLogSub)
|
||||
{
|
||||
_isFirstLogSub = false;
|
||||
await WriteInitialLog().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var state = ConnectionState;
|
||||
if (state == ConnectionState.Connecting || state == ConnectionState.Connected)
|
||||
await DisconnectInternalAsync(null).ConfigureAwait(false);
|
||||
|
||||
ConnectionState = ConnectionState.Connecting;
|
||||
await _rpcLogger.InfoAsync("Connecting").ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
_connectTask = new TaskCompletionSource<bool>();
|
||||
_cancelToken = new CancellationTokenSource();
|
||||
await ApiClient.ConnectAsync().ConfigureAwait(false);
|
||||
await _connectedEvent.InvokeAsync().ConfigureAwait(false);
|
||||
|
||||
/*if (_sessionId != null)
|
||||
await ApiClient.SendResumeAsync(_sessionId, _lastSeq).ConfigureAwait(false);
|
||||
else
|
||||
await ApiClient.SendIdentifyAsync().ConfigureAwait(false);*/
|
||||
|
||||
await _connectTask.Task.ConfigureAwait(false);
|
||||
|
||||
ConnectionState = ConnectionState.Connected;
|
||||
await _rpcLogger.InfoAsync("Connected").ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await DisconnectInternalAsync(null).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
_isReconnecting = false;
|
||||
await DisconnectInternalAsync(null).ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
private async Task DisconnectInternalAsync(Exception ex)
|
||||
{
|
||||
if (ConnectionState == ConnectionState.Disconnected) return;
|
||||
ConnectionState = ConnectionState.Disconnecting;
|
||||
await _rpcLogger.InfoAsync("Disconnecting").ConfigureAwait(false);
|
||||
|
||||
await _rpcLogger.DebugAsync("Disconnecting - CancelToken").ConfigureAwait(false);
|
||||
//Signal tasks to complete
|
||||
try { _cancelToken.Cancel(); } catch { }
|
||||
|
||||
await _rpcLogger.DebugAsync("Disconnecting - ApiClient").ConfigureAwait(false);
|
||||
//Disconnect from server
|
||||
await ApiClient.DisconnectAsync().ConfigureAwait(false);
|
||||
|
||||
_scopes = null;
|
||||
ConnectionState = ConnectionState.Disconnected;
|
||||
await _rpcLogger.InfoAsync("Disconnected").ConfigureAwait(false);
|
||||
|
||||
await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task StartReconnectAsync(Exception ex)
|
||||
{
|
||||
//TODO: Is this thread-safe?
|
||||
if (_reconnectTask != null) return;
|
||||
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await DisconnectInternalAsync(ex).ConfigureAwait(false);
|
||||
if (_reconnectTask != null) return;
|
||||
_isReconnecting = true;
|
||||
_reconnectTask = ReconnectInternalAsync();
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
private async Task ReconnectInternalAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
int nextReconnectDelay = 1000;
|
||||
while (_isReconnecting)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(nextReconnectDelay).ConfigureAwait(false);
|
||||
nextReconnectDelay *= 2;
|
||||
if (nextReconnectDelay > 30000)
|
||||
nextReconnectDelay = 30000;
|
||||
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await ConnectInternalAsync(_scopes).ConfigureAwait(false);
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _rpcLogger.WarningAsync("Reconnect failed", ex).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
_isReconnecting = false;
|
||||
_reconnectTask = null;
|
||||
}
|
||||
finally { _connectionLock.Release(); }
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessMessageAsync(string cmd, string evnt, object payload, string nonce)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (cmd)
|
||||
{
|
||||
case "DISPATCH":
|
||||
switch (evnt)
|
||||
{
|
||||
//Connection
|
||||
case "READY":
|
||||
{
|
||||
await _rpcLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false);
|
||||
var data = (payload as JToken).ToObject<ReadyEvent>(_serializer);
|
||||
|
||||
if (_scopes != null)
|
||||
await ApiClient.SendAuthorizeAsync(_scopes).ConfigureAwait(false); //No bearer
|
||||
else
|
||||
await ApiClient.SendAuthenticateAsync().ConfigureAwait(false); //Has bearer
|
||||
}
|
||||
break;
|
||||
|
||||
//Others
|
||||
default:
|
||||
await _rpcLogger.WarningAsync($"Unknown Dispatch ({evnt})").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "AUTHORIZE":
|
||||
{
|
||||
await _rpcLogger.DebugAsync("Received AUTHORIZE").ConfigureAwait(false);
|
||||
var data = (payload as JToken).ToObject<AuthorizeEvent>(_serializer);
|
||||
await ApiClient.LoginAsync(TokenType.Bearer, data.Code).ConfigureAwait(false);
|
||||
await ApiClient.SendAuthenticateAsync().ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
case "AUTHENTICATE":
|
||||
{
|
||||
await _rpcLogger.DebugAsync("Received AUTHENTICATE").ConfigureAwait(false);
|
||||
var data = (payload as JToken).ToObject<AuthenticateEvent>(_serializer);
|
||||
|
||||
var _ = _connectTask.TrySetResultAsync(true); //Signal the .Connect() call to complete
|
||||
await _rpcLogger.InfoAsync("Ready").ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
await _rpcLogger.WarningAsync($"Unknown OpCode ({cmd})").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _rpcLogger.ErrorAsync($"Error handling {cmd}{(evnt != null ? $" ({evnt})" : "")}", ex).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WriteInitialLog()
|
||||
{
|
||||
await _clientLogger.InfoAsync($"DiscordRpcClient v{DiscordRestConfig.Version} (RPC v{DiscordRpcConfig.RpcAPIVersion})").ConfigureAwait(false);
|
||||
await _clientLogger.VerboseAsync($"Runtime: {RuntimeInformation.FrameworkDescription.Trim()} ({ToArchString(RuntimeInformation.ProcessArchitecture)})").ConfigureAwait(false);
|
||||
await _clientLogger.VerboseAsync($"OS: {RuntimeInformation.OSDescription.Trim()} ({ToArchString(RuntimeInformation.OSArchitecture)})").ConfigureAwait(false);
|
||||
await _clientLogger.VerboseAsync($"Processors: {Environment.ProcessorCount}").ConfigureAwait(false);
|
||||
}
|
||||
private static string ToArchString(Architecture arch)
|
||||
{
|
||||
switch (arch)
|
||||
{
|
||||
case Architecture.X64: return "x64";
|
||||
case Architecture.X86: return "x86";
|
||||
default: return arch.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/Discord.Net/DiscordRpcConfig.cs
Normal file
23
src/Discord.Net/DiscordRpcConfig.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Discord.Net.WebSockets;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public class DiscordRpcConfig : DiscordConfig
|
||||
{
|
||||
public const int RpcAPIVersion = 1;
|
||||
|
||||
public const int PortRangeStart = 6463;
|
||||
public const int PortRangeEnd = 6472;
|
||||
|
||||
public DiscordRpcConfig(string clientId)
|
||||
{
|
||||
ClientId = clientId;
|
||||
}
|
||||
|
||||
/// <summary> Gets or sets the Discord client/application id used for this RPC connection. </summary>
|
||||
public string ClientId { get; set; }
|
||||
|
||||
/// <summary> Gets or sets the provider used to generate new websocket connections. </summary>
|
||||
public WebSocketProvider WebSocketProvider { get; set; } = () => new DefaultWebSocketClient();
|
||||
}
|
||||
}
|
||||
@@ -14,11 +14,6 @@ namespace Discord
|
||||
|
||||
/// <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; set; } = 0;
|
||||
/*/// <summary>
|
||||
/// Gets or sets whether the permissions cache should be used.
|
||||
/// This makes operations such as User.GetPermissions(Channel), User.GuildPermissions, Channel.GetUser, and Channel.Members much faster at the expense of increased memory usage.
|
||||
/// </summary>
|
||||
public bool UsePermissionsCache { get; set; } = false;*/
|
||||
/// <summary>
|
||||
/// Gets or sets the max number of users a guild may have for offline users to be included in the READY packet. Max is 250.
|
||||
/// Decreasing this may reduce CPU usage while increasing login time and network usage.
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Discord
|
||||
{
|
||||
protected ConcurrentDictionary<ulong, GroupUser> _users;
|
||||
private string _iconId;
|
||||
|
||||
|
||||
public override DiscordRestClient Discord { get; }
|
||||
public string Name { get; private set; }
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Discord
|
||||
private readonly DiscordSocketClient _discord;
|
||||
private readonly ISocketMessageChannel _channel;
|
||||
|
||||
public virtual IReadOnlyCollection<SocketMessage> Messages
|
||||
public virtual IReadOnlyCollection<SocketMessage> Messages
|
||||
=> ImmutableArray.Create<SocketMessage>();
|
||||
|
||||
public MessageManager(DiscordSocketClient discord, ISocketMessageChannel channel)
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
GeneralRest,
|
||||
DirectMessage,
|
||||
SendEditMessage,
|
||||
|
||||
GeneralGateway,
|
||||
UpdateStatus
|
||||
UpdateStatus,
|
||||
|
||||
GeneralRpc
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,10 @@ namespace Discord.Net.Queue
|
||||
|
||||
//Gateway
|
||||
[GlobalBucket.GeneralGateway] = new Bucket(null, "gateway", 120, 60, BucketTarget.Both),
|
||||
[GlobalBucket.UpdateStatus] = new Bucket(null, "status", 5, 1, BucketTarget.Both, GlobalBucket.GeneralGateway)
|
||||
[GlobalBucket.UpdateStatus] = new Bucket(null, "status", 5, 1, BucketTarget.Both, GlobalBucket.GeneralGateway),
|
||||
|
||||
//Rpc
|
||||
[GlobalBucket.GeneralRpc] = new Bucket(null, "rpc", 120, 60, BucketTarget.Both)
|
||||
}.ToImmutableDictionary();
|
||||
|
||||
_guildLimits = new Dictionary<GuildBucket, Bucket>
|
||||
|
||||
Reference in New Issue
Block a user