Added heartbeats, latency, guild events and channel events
This commit is contained in:
@@ -27,7 +27,7 @@ namespace Discord.API
|
||||
{
|
||||
public event Func<string, string, double, Task> SentRequest;
|
||||
public event Func<int, Task> SentGatewayMessage;
|
||||
public event Func<GatewayOpCode, string, JToken, Task> ReceivedGatewayEvent;
|
||||
public event Func<GatewayOpCode, int?, string, object, Task> ReceivedGatewayEvent;
|
||||
|
||||
private readonly RequestQueue _requestQueue;
|
||||
private readonly JsonSerializer _serializer;
|
||||
@@ -66,14 +66,14 @@ namespace Discord.API
|
||||
using (var reader = new StreamReader(decompressed))
|
||||
{
|
||||
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(reader.ReadToEnd());
|
||||
await ReceivedGatewayEvent.Raise((GatewayOpCode)msg.Operation, msg.Type, msg.Payload as JToken).ConfigureAwait(false);
|
||||
await ReceivedGatewayEvent.Raise((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
_gatewayClient.TextMessage += async text =>
|
||||
{
|
||||
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(text);
|
||||
await ReceivedGatewayEvent.Raise((GatewayOpCode)msg.Operation, msg.Type, msg.Payload as JToken).ConfigureAwait(false);
|
||||
await ReceivedGatewayEvent.Raise((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -363,6 +363,10 @@ namespace Discord.API
|
||||
};
|
||||
await SendGateway(GatewayOpCode.Identify, msg, options: options).ConfigureAwait(false);
|
||||
}
|
||||
public async Task SendHeartbeat(int lastSeq, RequestOptions options = null)
|
||||
{
|
||||
await SendGateway(GatewayOpCode.Heartbeat, lastSeq, options: options).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//Channels
|
||||
public async Task<Channel> GetChannel(ulong channelId, RequestOptions options = null)
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Discord.API
|
||||
[JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Type { get; set; }
|
||||
[JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public uint? Sequence { get; set; }
|
||||
public int? Sequence { get; set; }
|
||||
[JsonProperty("d")]
|
||||
public object Payload { get; set; }
|
||||
}
|
||||
|
||||
@@ -28,8 +28,10 @@ namespace Discord
|
||||
public LoginState LoginState { get; private set; }
|
||||
public API.DiscordApiClient ApiClient { get; private set; }
|
||||
|
||||
/// <summary> Creates a new discord client using only the REST API. </summary>
|
||||
public DiscordClient()
|
||||
: this(new DiscordConfig()) { }
|
||||
/// <summary> Creates a new discord client using only the REST API. </summary>
|
||||
public DiscordClient(DiscordConfig config)
|
||||
{
|
||||
_log = new LogManager(config.LogLevel);
|
||||
@@ -40,10 +42,12 @@ namespace Discord
|
||||
_connectionLock = new SemaphoreSlim(1, 1);
|
||||
_requestQueue = new RequestQueue();
|
||||
|
||||
//TODO: Is there any better way to do this WebSocketProvider access?
|
||||
ApiClient = new API.DiscordApiClient(config.RestClientProvider, (config as DiscordSocketConfig)?.WebSocketProvider, requestQueue: _requestQueue);
|
||||
ApiClient.SentRequest += async (method, endpoint, millis) => await _log.Verbose("Rest", $"{method} {endpoint}: {millis} ms").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Login(TokenType tokenType, string token, bool validateToken = true)
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
@@ -89,6 +93,7 @@ namespace Discord
|
||||
}
|
||||
protected virtual Task OnLogin() => Task.CompletedTask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Logout()
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
@@ -115,12 +120,14 @@ namespace Discord
|
||||
}
|
||||
protected virtual Task OnLogout() => Task.CompletedTask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyCollection<IConnection>> GetConnections()
|
||||
{
|
||||
var models = await ApiClient.GetCurrentUserConnections().ConfigureAwait(false);
|
||||
return models.Select(x => new Connection(x)).ToImmutableArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<IChannel> GetChannel(ulong id)
|
||||
{
|
||||
var model = await ApiClient.GetChannel(id).ConfigureAwait(false);
|
||||
@@ -140,12 +147,14 @@ namespace Discord
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<IReadOnlyCollection<IDMChannel>> GetDMChannels()
|
||||
{
|
||||
var models = await ApiClient.GetCurrentUserDMs().ConfigureAwait(false);
|
||||
return models.Select(x => new DMChannel(this, new User(this, x.Recipient), x)).ToImmutableArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<IInvite> GetInvite(string inviteIdOrXkcd)
|
||||
{
|
||||
var model = await ApiClient.GetInvite(inviteIdOrXkcd).ConfigureAwait(false);
|
||||
@@ -154,6 +163,7 @@ namespace Discord
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<IGuild> GetGuild(ulong id)
|
||||
{
|
||||
var model = await ApiClient.GetGuild(id).ConfigureAwait(false);
|
||||
@@ -161,6 +171,7 @@ namespace Discord
|
||||
return new Guild(this, model);
|
||||
return null;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<GuildEmbed?> GetGuildEmbed(ulong id)
|
||||
{
|
||||
var model = await ApiClient.GetGuildEmbed(id).ConfigureAwait(false);
|
||||
@@ -168,12 +179,14 @@ namespace Discord
|
||||
return new GuildEmbed(model);
|
||||
return null;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<IReadOnlyCollection<IUserGuild>> GetGuilds()
|
||||
{
|
||||
var models = await ApiClient.GetCurrentUserGuilds().ConfigureAwait(false);
|
||||
return models.Select(x => new UserGuild(this, x)).ToImmutableArray();
|
||||
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<IGuild> CreateGuild(string name, IVoiceRegion region, Stream jpegIcon = null)
|
||||
{
|
||||
var args = new CreateGuildParams();
|
||||
@@ -181,6 +194,7 @@ namespace Discord
|
||||
return new Guild(this, model);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<IUser> GetUser(ulong id)
|
||||
{
|
||||
var model = await ApiClient.GetUser(id).ConfigureAwait(false);
|
||||
@@ -188,6 +202,7 @@ namespace Discord
|
||||
return new User(this, model);
|
||||
return null;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<IUser> GetUser(string username, string discriminator)
|
||||
{
|
||||
var model = await ApiClient.GetUser(username, discriminator).ConfigureAwait(false);
|
||||
@@ -195,6 +210,7 @@ namespace Discord
|
||||
return new User(this, model);
|
||||
return null;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<ISelfUser> GetCurrentUser()
|
||||
{
|
||||
var user = _currentUser;
|
||||
@@ -206,17 +222,20 @@ namespace Discord
|
||||
}
|
||||
return user;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<IReadOnlyCollection<IUser>> QueryUsers(string query, int limit)
|
||||
{
|
||||
var models = await ApiClient.QueryUsers(query, limit).ConfigureAwait(false);
|
||||
return models.Select(x => new User(this, x)).ToImmutableArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<IReadOnlyCollection<IVoiceRegion>> GetVoiceRegions()
|
||||
{
|
||||
var models = await ApiClient.GetVoiceRegions().ConfigureAwait(false);
|
||||
return models.Select(x => new VoiceRegion(x)).ToImmutableArray();
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<IVoiceRegion> GetVoiceRegion(string id)
|
||||
{
|
||||
var models = await ApiClient.GetVoiceRegions().ConfigureAwait(false);
|
||||
@@ -228,6 +247,7 @@ namespace Discord
|
||||
if (!_isDisposed)
|
||||
_isDisposed = true;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Discord.API;
|
||||
using Discord.API.Gateway;
|
||||
using Discord.API.Gateway;
|
||||
using Discord.Data;
|
||||
using Discord.Extensions;
|
||||
using Discord.Logging;
|
||||
@@ -11,19 +10,23 @@ using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
//TODO: Remove unnecessary `as` casts
|
||||
//TODO: Add docstrings
|
||||
//TODO: Add event docstrings
|
||||
//TODO: Add reconnect logic (+ensure the heartbeat task shuts down)
|
||||
//TODO: Add resume logic
|
||||
public class DiscordSocketClient : DiscordClient, IDiscordClient
|
||||
{
|
||||
public event Func<Task> Connected, Disconnected;
|
||||
public event Func<Task> Ready;
|
||||
//public event Func<Channel> VoiceConnected, VoiceDisconnected;
|
||||
/*public event Func<IChannel, Task> ChannelCreated, ChannelDestroyed;
|
||||
public event Func<IChannel, Task> ChannelCreated, ChannelDestroyed;
|
||||
public event Func<IChannel, IChannel, Task> ChannelUpdated;
|
||||
public event Func<IMessage, Task> MessageReceived, MessageDeleted;
|
||||
public event Func<IMessage, IMessage, Task> MessageUpdated;
|
||||
@@ -34,7 +37,8 @@ namespace Discord
|
||||
public event Func<IUser, Task> UserJoined, UserLeft, UserBanned, UserUnbanned;
|
||||
public event Func<IUser, IUser, Task> UserUpdated;
|
||||
public event Func<ISelfUser, ISelfUser, Task> CurrentUserUpdated;
|
||||
public event Func<IChannel, IUser, Task> UserIsTyping;*/
|
||||
public event Func<IChannel, IUser, Task> UserIsTyping;
|
||||
public event Func<int, Task> LatencyUpdated;
|
||||
|
||||
private readonly ConcurrentQueue<ulong> _largeGuilds;
|
||||
private readonly Logger _gatewayLogger;
|
||||
@@ -44,13 +48,21 @@ namespace Discord
|
||||
private readonly bool _enablePreUpdateEvents;
|
||||
private readonly int _largeThreshold;
|
||||
private readonly int _totalShards;
|
||||
private ImmutableDictionary<string, VoiceRegion> _voiceRegions;
|
||||
private string _sessionId;
|
||||
private int _lastSeq;
|
||||
private ImmutableDictionary<string, VoiceRegion> _voiceRegions;
|
||||
private TaskCompletionSource<bool> _connectTask;
|
||||
private CancellationTokenSource _heartbeatCancelToken;
|
||||
private Task _heartbeatTask;
|
||||
private long _heartbeatTime;
|
||||
|
||||
/// <summary> Gets the shard if of this client. </summary>
|
||||
public int ShardId { get; }
|
||||
/// <summary> Gets the current connection state of this client. </summary>
|
||||
public ConnectionState ConnectionState { get; private set; }
|
||||
public IWebSocketClient GatewaySocket { get; private set; }
|
||||
/// <summary> Gets the estimated round-trip latency to the gateway server. </summary>
|
||||
public int Latency { get; private set; }
|
||||
internal IWebSocketClient GatewaySocket { get; private set; }
|
||||
internal int MessageCacheSize { get; private set; }
|
||||
//internal bool UsePermissionCache { get; private set; }
|
||||
internal DataStore DataStore { get; private set; }
|
||||
@@ -61,7 +73,7 @@ namespace Discord
|
||||
get
|
||||
{
|
||||
var guilds = DataStore.Guilds;
|
||||
return guilds.Select(x => x as CachedGuild).ToReadOnlyCollection(guilds);
|
||||
return guilds.ToReadOnlyCollection(guilds);
|
||||
}
|
||||
}
|
||||
internal IReadOnlyCollection<CachedDMChannel> DMChannels
|
||||
@@ -69,13 +81,15 @@ namespace Discord
|
||||
get
|
||||
{
|
||||
var users = DataStore.Users;
|
||||
return users.Select(x => (x as CachedPublicUser).DMChannel).Where(x => x != null).ToReadOnlyCollection(users);
|
||||
return users.Select(x => x.DMChannel).Where(x => x != null).ToReadOnlyCollection(users);
|
||||
}
|
||||
}
|
||||
internal IReadOnlyCollection<VoiceRegion> VoiceRegions => _voiceRegions.ToReadOnlyCollection();
|
||||
|
||||
/// <summary> Creates a new discord client using the REST and WebSocket APIs. </summary>
|
||||
public DiscordSocketClient()
|
||||
: this(new DiscordSocketConfig()) { }
|
||||
/// <summary> Creates a new discord client using the REST and WebSocket APIs. </summary>
|
||||
public DiscordSocketClient(DiscordSocketConfig config)
|
||||
: base(config)
|
||||
{
|
||||
@@ -117,6 +131,7 @@ namespace Discord
|
||||
_voiceRegions = ImmutableDictionary.Create<string, VoiceRegion>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Connect()
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
@@ -135,6 +150,7 @@ namespace Discord
|
||||
try
|
||||
{
|
||||
_connectTask = new TaskCompletionSource<bool>();
|
||||
_heartbeatCancelToken = new CancellationTokenSource();
|
||||
await ApiClient.Connect().ConfigureAwait(false);
|
||||
|
||||
await _connectTask.Task.ConfigureAwait(false);
|
||||
@@ -148,6 +164,7 @@ namespace Discord
|
||||
|
||||
await Connected.Raise().ConfigureAwait(false);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public async Task Disconnect()
|
||||
{
|
||||
await _connectionLock.WaitAsync().ConfigureAwait(false);
|
||||
@@ -165,13 +182,15 @@ namespace Discord
|
||||
ConnectionState = ConnectionState.Disconnecting;
|
||||
|
||||
await ApiClient.Disconnect().ConfigureAwait(false);
|
||||
await _heartbeatTask.ConfigureAwait(false);
|
||||
while (_largeGuilds.TryDequeue(out guildId)) { }
|
||||
|
||||
ConnectionState = ConnectionState.Disconnected;
|
||||
|
||||
await Disconnected.Raise().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IVoiceRegion> GetVoiceRegion(string id)
|
||||
{
|
||||
VoiceRegion region;
|
||||
@@ -180,6 +199,7 @@ namespace Discord
|
||||
return Task.FromResult<IVoiceRegion>(null);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IGuild> GetGuild(ulong id)
|
||||
{
|
||||
return Task.FromResult<IGuild>(DataStore.GetGuild(id));
|
||||
@@ -192,7 +212,7 @@ namespace Discord
|
||||
if (model.Unavailable != true)
|
||||
{
|
||||
for (int i = 0; i < model.Channels.Length; i++)
|
||||
AddCachedChannel(model.Channels[i], dataStore);
|
||||
AddCachedChannel(guild, model.Channels[i], dataStore);
|
||||
}
|
||||
dataStore.AddGuild(guild);
|
||||
if (model.Large)
|
||||
@@ -203,7 +223,7 @@ namespace Discord
|
||||
{
|
||||
dataStore = dataStore ?? DataStore;
|
||||
|
||||
var guild = dataStore.RemoveGuild(id) as CachedGuild;
|
||||
var guild = dataStore.RemoveGuild(id);
|
||||
foreach (var channel in guild.Channels)
|
||||
guild.RemoveCachedChannel(channel.Id);
|
||||
foreach (var user in guild.Members)
|
||||
@@ -211,25 +231,25 @@ namespace Discord
|
||||
return guild;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IChannel> GetChannel(ulong id)
|
||||
{
|
||||
return Task.FromResult<IChannel>(DataStore.GetChannel(id));
|
||||
}
|
||||
internal ICachedChannel AddCachedChannel(API.Channel model, DataStore dataStore = null)
|
||||
internal ICachedGuildChannel AddCachedChannel(CachedGuild guild, API.Channel model, DataStore dataStore = null)
|
||||
{
|
||||
dataStore = dataStore ?? DataStore;
|
||||
|
||||
ICachedChannel channel;
|
||||
if (model.IsPrivate)
|
||||
{
|
||||
var recipient = AddCachedUser(model.Recipient, dataStore);
|
||||
channel = recipient.SetDMChannel(model);
|
||||
}
|
||||
else
|
||||
{
|
||||
var guild = dataStore.GetGuild(model.GuildId.Value);
|
||||
channel = guild.AddCachedChannel(model);
|
||||
}
|
||||
var channel = guild.AddCachedChannel(model);
|
||||
dataStore.AddChannel(channel);
|
||||
return channel;
|
||||
}
|
||||
internal CachedDMChannel AddCachedDMChannel(API.Channel model, DataStore dataStore = null)
|
||||
{
|
||||
dataStore = dataStore ?? DataStore;
|
||||
|
||||
var recipient = AddCachedUser(model.Recipient, dataStore);
|
||||
var channel = recipient.AddDMChannel(model);
|
||||
dataStore.AddChannel(channel);
|
||||
return channel;
|
||||
}
|
||||
@@ -237,8 +257,8 @@ namespace Discord
|
||||
{
|
||||
dataStore = dataStore ?? DataStore;
|
||||
|
||||
//TODO: C#7
|
||||
var channel = DataStore.RemoveChannel(id) as ICachedChannel;
|
||||
//TODO: C#7 Typeswitch Candidate
|
||||
var channel = DataStore.RemoveChannel(id);
|
||||
|
||||
var guildChannel = channel as ICachedGuildChannel;
|
||||
if (guildChannel != null)
|
||||
@@ -258,10 +278,12 @@ namespace Discord
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IUser> GetUser(ulong id)
|
||||
{
|
||||
return Task.FromResult<IUser>(DataStore.GetUser(id));
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public override Task<IUser> GetUser(string username, string discriminator)
|
||||
{
|
||||
return Task.FromResult<IUser>(DataStore.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault());
|
||||
@@ -270,7 +292,7 @@ namespace Discord
|
||||
{
|
||||
dataStore = dataStore ?? DataStore;
|
||||
|
||||
var user = dataStore.GetOrAddUser(model.Id, _ => new CachedPublicUser(this, model)) as CachedPublicUser;
|
||||
var user = dataStore.GetOrAddUser(model.Id, _ => new CachedPublicUser(this, model));
|
||||
user.AddRef();
|
||||
return user;
|
||||
}
|
||||
@@ -278,22 +300,34 @@ namespace Discord
|
||||
{
|
||||
dataStore = dataStore ?? DataStore;
|
||||
|
||||
var user = dataStore.GetUser(id) as CachedPublicUser;
|
||||
var user = dataStore.GetUser(id);
|
||||
user.RemoveRef();
|
||||
return user;
|
||||
}
|
||||
|
||||
private async Task ProcessMessage(GatewayOpCode opCode, string type, JToken payload)
|
||||
private async Task ProcessMessage(GatewayOpCode opCode, int? seq, string type, object payload)
|
||||
{
|
||||
if (seq != null)
|
||||
_lastSeq = seq.Value;
|
||||
try
|
||||
{
|
||||
switch (opCode)
|
||||
{
|
||||
case GatewayOpCode.Hello:
|
||||
{
|
||||
var data = payload.ToObject<HelloEvent>(_serializer);
|
||||
var data = (payload as JToken).ToObject<HelloEvent>(_serializer);
|
||||
|
||||
await ApiClient.SendIdentify().ConfigureAwait(false);
|
||||
_heartbeatTask = RunHeartbeat(data.HeartbeatInterval, _heartbeatCancelToken.Token);
|
||||
}
|
||||
break;
|
||||
case GatewayOpCode.HeartbeatAck:
|
||||
{
|
||||
var latency = (int)(Environment.TickCount - _heartbeatTime);
|
||||
await _gatewayLogger.Debug($"Latency: {latency} ms").ConfigureAwait(false);
|
||||
Latency = latency;
|
||||
|
||||
await LatencyUpdated.Raise(latency).ConfigureAwait(false);
|
||||
}
|
||||
break;
|
||||
case GatewayOpCode.Dispatch:
|
||||
@@ -303,15 +337,15 @@ namespace Discord
|
||||
case "READY":
|
||||
{
|
||||
//TODO: Make downloading large guilds optional
|
||||
var data = payload.ToObject<ReadyEvent>(_serializer);
|
||||
var data = (payload as JToken).ToObject<ReadyEvent>(_serializer);
|
||||
var dataStore = _dataStoreProvider(ShardId, _totalShards, data.Guilds.Length, data.PrivateChannels.Length);
|
||||
|
||||
_currentUser = new CachedSelfUser(this,data.User);
|
||||
_currentUser = new CachedSelfUser(this, data.User);
|
||||
|
||||
for (int i = 0; i < data.Guilds.Length; i++)
|
||||
AddCachedGuild(data.Guilds[i], dataStore);
|
||||
for (int i = 0; i < data.PrivateChannels.Length; i++)
|
||||
AddCachedChannel(data.PrivateChannels[i], dataStore);
|
||||
AddCachedDMChannel(data.PrivateChannels[i], dataStore);
|
||||
|
||||
_sessionId = data.SessionId;
|
||||
DataStore = dataStore;
|
||||
@@ -323,9 +357,9 @@ namespace Discord
|
||||
break;
|
||||
|
||||
//Guilds
|
||||
/*case "GUILD_CREATE":
|
||||
case "GUILD_CREATE":
|
||||
{
|
||||
var data = payload.ToObject<ExtendedGuild>(_serializer);
|
||||
var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer);
|
||||
var guild = new CachedGuild(this, data);
|
||||
DataStore.AddGuild(guild);
|
||||
|
||||
@@ -342,12 +376,12 @@ namespace Discord
|
||||
break;
|
||||
case "GUILD_UPDATE":
|
||||
{
|
||||
var data = payload.ToObject<API.Guild>(_serializer);
|
||||
var data = (payload as JToken).ToObject<API.Guild>(_serializer);
|
||||
var guild = DataStore.GetGuild(data.Id);
|
||||
if (guild != null)
|
||||
{
|
||||
var before = _enablePreUpdateEvents ? guild.Clone() : null;
|
||||
guild.Update(data);
|
||||
guild.Update(data, UpdateSource.WebSocket);
|
||||
await GuildUpdated.Raise(before, guild);
|
||||
}
|
||||
else
|
||||
@@ -356,7 +390,7 @@ namespace Discord
|
||||
break;
|
||||
case "GUILD_DELETE":
|
||||
{
|
||||
var data = payload.ToObject<ExtendedGuild>(_serializer);
|
||||
var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer);
|
||||
var guild = DataStore.RemoveGuild(data.Id);
|
||||
if (guild != null)
|
||||
{
|
||||
@@ -375,34 +409,34 @@ namespace Discord
|
||||
//Channels
|
||||
case "CHANNEL_CREATE":
|
||||
{
|
||||
var data = payload.ToObject<API.Channel>(_serializer);
|
||||
var data = (payload as JToken).ToObject<API.Channel>(_serializer);
|
||||
|
||||
IChannel channel = null;
|
||||
ICachedChannel channel = null;
|
||||
if (data.GuildId != null)
|
||||
{
|
||||
var guild = GetCachedGuild(data.GuildId.Value);
|
||||
var guild = DataStore.GetGuild(data.GuildId.Value);
|
||||
if (guild != null)
|
||||
channel = guild.AddCachedChannel(data.Id, true);
|
||||
{
|
||||
channel = guild.AddCachedChannel(data);
|
||||
DataStore.AddChannel(channel);
|
||||
}
|
||||
else
|
||||
await _gatewayLogger.Warning("CHANNEL_CREATE referenced an unknown guild.");
|
||||
}
|
||||
else
|
||||
channel = AddCachedPrivateChannel(data.Id, data.Recipient.Id);
|
||||
channel = AddCachedDMChannel(data);
|
||||
if (channel != null)
|
||||
{
|
||||
channel.Update(data);
|
||||
await ChannelCreated.Raise(channel);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "CHANNEL_UPDATE":
|
||||
{
|
||||
var data = payload.ToObject<API.Channel>(_serializer);
|
||||
var channel = DataStore.GetChannel(data.Id) as Channel;
|
||||
var data = (payload as JToken).ToObject<API.Channel>(_serializer);
|
||||
var channel = DataStore.GetChannel(data.Id);
|
||||
if (channel != null)
|
||||
{
|
||||
var before = _enablePreUpdateEvents ? channel.Clone() : null;
|
||||
channel.Update(data);
|
||||
channel.Update(data, UpdateSource.WebSocket);
|
||||
await ChannelUpdated.Raise(before, channel);
|
||||
}
|
||||
else
|
||||
@@ -411,7 +445,7 @@ namespace Discord
|
||||
break;
|
||||
case "CHANNEL_DELETE":
|
||||
{
|
||||
var data = payload.ToObject<API.Channel>(_serializer);
|
||||
var data = (payload as JToken).ToObject<API.Channel>(_serializer);
|
||||
var channel = RemoveCachedChannel(data.Id);
|
||||
if (channel != null)
|
||||
await ChannelDestroyed.Raise(channel);
|
||||
@@ -421,9 +455,9 @@ namespace Discord
|
||||
break;
|
||||
|
||||
//Members
|
||||
case "GUILD_MEMBER_ADD":
|
||||
/*case "GUILD_MEMBER_ADD":
|
||||
{
|
||||
var data = payload.ToObject<API.GuildMember>(_serializer);
|
||||
var data = (payload as JToken).ToObject<API.GuildMember>(_serializer);
|
||||
var guild = GetGuild(data.GuildId.Value);
|
||||
if (guild != null)
|
||||
{
|
||||
@@ -438,7 +472,7 @@ namespace Discord
|
||||
break;
|
||||
case "GUILD_MEMBER_UPDATE":
|
||||
{
|
||||
var data = payload.ToObject<API.GuildMember>(_serializer);
|
||||
var data = (payload as JToken).ToObject<API.GuildMember>(_serializer);
|
||||
var guild = GetGuild(data.GuildId.Value);
|
||||
if (guild != null)
|
||||
{
|
||||
@@ -458,7 +492,7 @@ namespace Discord
|
||||
break;
|
||||
case "GUILD_MEMBER_REMOVE":
|
||||
{
|
||||
var data = payload.ToObject<API.GuildMember>(_serializer);
|
||||
var data = (payload as JToken).ToObject<API.GuildMember>(_serializer);
|
||||
var guild = GetGuild(data.GuildId.Value);
|
||||
if (guild != null)
|
||||
{
|
||||
@@ -479,7 +513,7 @@ namespace Discord
|
||||
break;
|
||||
case "GUILD_MEMBERS_CHUNK":
|
||||
{
|
||||
var data = payload.ToObject<GuildMembersChunkEvent>(_serializer);
|
||||
var data = (payload as JToken).ToObject<GuildMembersChunkEvent>(_serializer);
|
||||
var guild = GetCachedGuild(data.GuildId);
|
||||
if (guild != null)
|
||||
{
|
||||
@@ -498,9 +532,9 @@ namespace Discord
|
||||
break;
|
||||
|
||||
//Roles
|
||||
case "GUILD_ROLE_CREATE":
|
||||
/*case "GUILD_ROLE_CREATE":
|
||||
{
|
||||
var data = payload.ToObject<GuildRoleCreateEvent>(_serializer);
|
||||
var data = (payload as JToken).ToObject<GuildRoleCreateEvent>(_serializer);
|
||||
var guild = GetCachedGuild(data.GuildId);
|
||||
if (guild != null)
|
||||
{
|
||||
@@ -514,7 +548,7 @@ namespace Discord
|
||||
break;
|
||||
case "GUILD_ROLE_UPDATE":
|
||||
{
|
||||
var data = payload.ToObject<GuildRoleUpdateEvent>(_serializer);
|
||||
var data = (payload as JToken).ToObject<GuildRoleUpdateEvent>(_serializer);
|
||||
var guild = GetCachedGuild(data.GuildId);
|
||||
if (guild != null)
|
||||
{
|
||||
@@ -534,8 +568,8 @@ namespace Discord
|
||||
break;
|
||||
case "GUILD_ROLE_DELETE":
|
||||
{
|
||||
var data = payload.ToObject<GuildRoleDeleteEvent>(_serializer);
|
||||
var guild = DataStore.GetGuild(data.GuildId) as CachedGuild;
|
||||
var data = (payload as JToken).ToObject<GuildRoleDeleteEvent>(_serializer);
|
||||
var guild = DataStore.GetGuild(data.GuildId);
|
||||
if (guild != null)
|
||||
{
|
||||
var role = guild.RemoveRole(data.RoleId);
|
||||
@@ -552,7 +586,7 @@ namespace Discord
|
||||
//Bans
|
||||
case "GUILD_BAN_ADD":
|
||||
{
|
||||
var data = payload.ToObject<GuildBanEvent>(_serializer);
|
||||
var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer);
|
||||
var guild = GetCachedGuild(data.GuildId);
|
||||
if (guild != null)
|
||||
await UserBanned.Raise(new User(this, data));
|
||||
@@ -574,8 +608,7 @@ namespace Discord
|
||||
//Messages
|
||||
case "MESSAGE_CREATE":
|
||||
{
|
||||
var data = payload.ToObject<API.Message>(_serializer);
|
||||
|
||||
var data = (payload as JToken).ToObject<API.Message>(_serializer);
|
||||
var channel = DataStore.GetChannel(data.ChannelId);
|
||||
if (channel != null)
|
||||
{
|
||||
@@ -599,7 +632,7 @@ namespace Discord
|
||||
break;
|
||||
case "MESSAGE_UPDATE":
|
||||
{
|
||||
var data = payload.ToObject<API.Message>(_serializer);
|
||||
var data = (payload as JToken).ToObject<API.Message>(_serializer);
|
||||
var channel = GetCachedChannel(data.ChannelId);
|
||||
if (channel != null)
|
||||
{
|
||||
@@ -614,7 +647,7 @@ namespace Discord
|
||||
break;
|
||||
case "MESSAGE_DELETE":
|
||||
{
|
||||
var data = payload.ToObject<API.Message>(_serializer);
|
||||
var data = (payload as JToken).ToObject<API.Message>(_serializer);
|
||||
var channel = GetCachedChannel(data.ChannelId);
|
||||
if (channel != null)
|
||||
{
|
||||
@@ -629,7 +662,7 @@ namespace Discord
|
||||
//Statuses
|
||||
case "PRESENCE_UPDATE":
|
||||
{
|
||||
var data = payload.ToObject<API.Presence>(_serializer);
|
||||
var data = (payload as JToken).ToObject<API.Presence>(_serializer);
|
||||
User user;
|
||||
Guild guild;
|
||||
if (data.GuildId == null)
|
||||
@@ -664,7 +697,7 @@ namespace Discord
|
||||
break;
|
||||
case "TYPING_START":
|
||||
{
|
||||
var data = payload.ToObject<TypingStartEvent>(_serializer);
|
||||
var data = (payload as JToken).ToObject<TypingStartEvent>(_serializer);
|
||||
var channel = GetCachedChannel(data.ChannelId);
|
||||
if (channel != null)
|
||||
{
|
||||
@@ -683,7 +716,7 @@ namespace Discord
|
||||
//Voice
|
||||
case "VOICE_STATE_UPDATE":
|
||||
{
|
||||
var data = payload.ToObject<API.VoiceState>(_serializer);
|
||||
var data = (payload as JToken).ToObject<API.VoiceState>(_serializer);
|
||||
var guild = GetGuild(data.GuildId);
|
||||
if (guild != null)
|
||||
{
|
||||
@@ -708,7 +741,7 @@ namespace Discord
|
||||
//Settings
|
||||
case "USER_UPDATE":
|
||||
{
|
||||
var data = payload.ToObject<SelfUser>(_serializer);
|
||||
var data = (payload as JToken).ToObject<SelfUser>(_serializer);
|
||||
if (data.Id == CurrentUser.Id)
|
||||
{
|
||||
var before = _enablePreUpdateEvents ? CurrentUser.Clone() : null;
|
||||
@@ -746,5 +779,17 @@ namespace Discord
|
||||
}
|
||||
await _gatewayLogger.Debug($"Received {opCode}{(type != null ? $" ({type})" : "")}").ConfigureAwait(false);
|
||||
}
|
||||
private async Task RunHeartbeat(int intervalMillis, CancellationToken cancelToken)
|
||||
{
|
||||
var state = ConnectionState;
|
||||
while (state == ConnectionState.Connecting || state == ConnectionState.Connected)
|
||||
{
|
||||
//if (_heartbeatTime != 0) //TODO: Connection lost, reconnect
|
||||
|
||||
_heartbeatTime = Environment.TickCount;
|
||||
await ApiClient.SendHeartbeat(_lastSeq).ConfigureAwait(false);
|
||||
await Task.Delay(intervalMillis, cancelToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Discord
|
||||
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
protected void Update(Model model, UpdateSource source)
|
||||
public void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Discord
|
||||
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
protected virtual void Update(Model model, UpdateSource source)
|
||||
public virtual void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Discord
|
||||
: base(guild, model)
|
||||
{
|
||||
}
|
||||
protected override void Update(Model model, UpdateSource source)
|
||||
public override void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Discord
|
||||
: base(guild, model)
|
||||
{
|
||||
}
|
||||
protected override void Update(Model model, UpdateSource source)
|
||||
public override void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Discord
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
|
||||
private void Update(Model model, UpdateSource source)
|
||||
public void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Discord
|
||||
ExpireGracePeriod = model.ExpireGracePeriod;
|
||||
SyncedAt = model.SyncedAt;
|
||||
|
||||
Role = Guild.GetRole(model.RoleId) as Role;
|
||||
Role = Guild.GetRole(model.RoleId);
|
||||
User = new User(Discord, model.User);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Discord
|
||||
Discord = discord;
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
private void Update(Model model, UpdateSource source)
|
||||
public void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Discord
|
||||
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
protected void Update(Model model, UpdateSource source)
|
||||
public void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Discord
|
||||
{
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
private void Update(Model model, UpdateSource source)
|
||||
public void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Discord
|
||||
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
private void Update(Model model, UpdateSource source)
|
||||
public void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
|
||||
@@ -130,27 +130,14 @@ namespace Discord
|
||||
perms = channel.GetPermissionOverwrite(user);
|
||||
if (perms != null)
|
||||
resolvedPermissions = (resolvedPermissions & ~perms.Value.DenyValue) | perms.Value.AllowValue;
|
||||
|
||||
#if CSHARP7
|
||||
switch (channel)
|
||||
{
|
||||
case ITextChannel _:
|
||||
if (!GetValue(resolvedPermissions, ChannelPermission.ReadMessages))
|
||||
resolvedPermissions = 0; //No read permission on a text channel removes all other permissions
|
||||
break;
|
||||
case IVoiceChannel _:
|
||||
if (!GetValue(resolvedPermissions, ChannelPermission.Connect))
|
||||
resolvedPermissions = 0; //No read permission on a text channel removes all other permissions
|
||||
break;
|
||||
}
|
||||
#else
|
||||
|
||||
//TODO: C# Typeswitch candidate
|
||||
var textChannel = channel as ITextChannel;
|
||||
var voiceChannel = channel as IVoiceChannel;
|
||||
if (textChannel != null && !GetValue(resolvedPermissions, ChannelPermission.ReadMessages))
|
||||
resolvedPermissions = 0; //No read permission on a text channel removes all other permissions
|
||||
else if (voiceChannel != null && !GetValue(resolvedPermissions, ChannelPermission.Connect))
|
||||
resolvedPermissions = 0; //No connect permission on a voice channel removes all other permissions
|
||||
#endif
|
||||
resolvedPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from guildPerms, for example)
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace Discord
|
||||
|
||||
Update(model, UpdateSource.Creation);
|
||||
}
|
||||
private void Update(Model model, UpdateSource source)
|
||||
public void Update(Model model, UpdateSource source)
|
||||
{
|
||||
if (source == UpdateSource.Rest && IsAttached) return;
|
||||
|
||||
@@ -49,9 +49,9 @@ namespace Discord
|
||||
Nickname = model.Nick;
|
||||
|
||||
var roles = ImmutableArray.CreateBuilder<Role>(model.Roles.Length + 1);
|
||||
roles.Add(Guild.EveryoneRole as Role);
|
||||
roles.Add(Guild.EveryoneRole);
|
||||
for (int i = 0; i < model.Roles.Length; i++)
|
||||
roles.Add(Guild.GetRole(model.Roles[i]) as Role);
|
||||
roles.Add(Guild.GetRole(model.Roles[i]));
|
||||
Roles = roles.ToImmutable();
|
||||
|
||||
GuildPermissions = new GuildPermissions(Permissions.ResolveGuild(this));
|
||||
@@ -89,7 +89,7 @@ namespace Discord
|
||||
if (args.Nickname.IsSpecified)
|
||||
Nickname = args.Nickname.Value ?? "";
|
||||
if (args.Roles.IsSpecified)
|
||||
Roles = args.Roles.Value.Select(x => Guild.GetRole(x) as Role).Where(x => x != null).ToImmutableArray();
|
||||
Roles = args.Roles.Value.Select(x => Guild.GetRole(x)).Where(x => x != null).ToImmutableArray();
|
||||
}
|
||||
}
|
||||
public async Task Kick()
|
||||
|
||||
@@ -65,6 +65,7 @@ namespace Discord
|
||||
|
||||
public CachedDMChannel Clone() => MemberwiseClone() as CachedDMChannel;
|
||||
|
||||
IMessage IMessageChannel.GetCachedMessage(ulong id) => GetCachedMessage(id);
|
||||
IMessage IMessageChannel.GetCachedMessage(ulong id) => GetCachedMessage(id);
|
||||
ICachedChannel ICachedChannel.Clone() => Clone();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +153,8 @@ namespace Discord
|
||||
return null;
|
||||
}
|
||||
|
||||
public CachedGuild Clone() => MemberwiseClone() as CachedGuild;
|
||||
|
||||
new internal ICachedGuildChannel ToChannel(ChannelModel model)
|
||||
{
|
||||
switch (model.Type)
|
||||
|
||||
@@ -12,5 +12,7 @@ namespace Discord
|
||||
: base(guild, user, model)
|
||||
{
|
||||
}
|
||||
|
||||
public CachedGuildUser Clone() => MemberwiseClone() as CachedGuildUser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Discord
|
||||
{
|
||||
}
|
||||
|
||||
public CachedDMChannel SetDMChannel(ChannelModel model)
|
||||
public CachedDMChannel AddDMChannel(ChannelModel model)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
|
||||
@@ -69,5 +69,6 @@ namespace Discord
|
||||
|
||||
IMessage IMessageChannel.GetCachedMessage(ulong id) => GetCachedMessage(id);
|
||||
IUser ICachedMessageChannel.GetCachedUser(ulong id) => GetCachedUser(id);
|
||||
ICachedChannel ICachedChannel.Clone() => Clone();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,5 +34,7 @@ namespace Discord
|
||||
}
|
||||
|
||||
public CachedVoiceChannel Clone() => MemberwiseClone() as CachedVoiceChannel;
|
||||
|
||||
ICachedChannel ICachedChannel.Clone() => Clone();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
namespace Discord
|
||||
using Model = Discord.API.Channel;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
internal interface ICachedChannel : IChannel, ICachedEntity<ulong>
|
||||
{
|
||||
void Update(Model model, UpdateSource source);
|
||||
|
||||
ICachedChannel Clone();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Discord.Extensions
|
||||
internal static class EventExtensions
|
||||
{
|
||||
//TODO: Optimize these for if there is only 1 subscriber (can we do this?)
|
||||
//TODO: Could we maintain our own list instead of generating one on every invocation?
|
||||
public static async Task Raise(this Func<Task> eventHandler)
|
||||
{
|
||||
var subscriptions = eventHandler?.GetInvocationList();
|
||||
@@ -42,5 +43,14 @@ namespace Discord.Extensions
|
||||
await (subscriptions[i] as Func<T1, T2, T3, Task>).Invoke(arg1, arg2, arg3).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
public static async Task Raise<T1, T2, T3, T4>(this Func<T1, T2, T3, T4, Task> eventHandler, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
|
||||
{
|
||||
var subscriptions = eventHandler?.GetInvocationList();
|
||||
if (subscriptions != null)
|
||||
{
|
||||
for (int i = 0; i < subscriptions.Length; i++)
|
||||
await (subscriptions[i] as Func<T1, T2, T3, T4, Task>).Invoke(arg1, arg2, arg3, arg4).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,25 +92,7 @@ namespace Discord.Net.Rest
|
||||
{
|
||||
foreach (var p in multipartParams)
|
||||
{
|
||||
#if CSHARP7
|
||||
switch (p.Value)
|
||||
{
|
||||
case string value:
|
||||
content.Add(new StringContent(value), p.Key);
|
||||
break;
|
||||
case byte[] value:
|
||||
content.Add(new ByteArrayContent(value), p.Key);
|
||||
break;
|
||||
case Stream value:
|
||||
content.Add(new StreamContent(value), p.Key);
|
||||
break;
|
||||
case MultipartFile value:
|
||||
content.Add(new StreamContent(value.Stream), value.Filename, p.Key);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\"");
|
||||
}
|
||||
#else
|
||||
//TODO: C# Typeswitch candidate
|
||||
var stringValue = p.Value as string;
|
||||
if (stringValue != null) { content.Add(new StringContent(stringValue), p.Key); continue; }
|
||||
var byteArrayValue = p.Value as byte[];
|
||||
@@ -125,7 +107,6 @@ namespace Discord.Net.Rest
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\"");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
restRequest.Content = content;
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Discord.Net.WebSockets
|
||||
public event Func<string, Task> TextMessage;
|
||||
|
||||
private readonly ClientWebSocket _client;
|
||||
private readonly SemaphoreSlim _sendLock;
|
||||
private Task _task;
|
||||
private CancellationTokenSource _cancelTokenSource;
|
||||
private CancellationToken _cancelToken, _parentToken;
|
||||
@@ -30,6 +31,7 @@ namespace Discord.Net.WebSockets
|
||||
_client.Options.Proxy = null;
|
||||
_client.Options.KeepAliveInterval = TimeSpan.Zero;
|
||||
|
||||
_sendLock = new SemaphoreSlim(1, 1);
|
||||
_cancelTokenSource = new CancellationTokenSource();
|
||||
_cancelToken = CancellationToken.None;
|
||||
_parentToken = CancellationToken.None;
|
||||
@@ -82,28 +84,37 @@ namespace Discord.Net.WebSockets
|
||||
|
||||
public async Task Send(byte[] data, int index, int count, bool isText)
|
||||
{
|
||||
//TODO: If connection is temporarily down, retry?
|
||||
int frameCount = (int)Math.Ceiling((double)count / SendChunkSize);
|
||||
|
||||
for (int i = 0; i < frameCount; i++, index += SendChunkSize)
|
||||
await _sendLock.WaitAsync(_cancelToken);
|
||||
try
|
||||
{
|
||||
bool isLast = i == (frameCount - 1);
|
||||
//TODO: If connection is temporarily down, retry?
|
||||
int frameCount = (int)Math.Ceiling((double)count / SendChunkSize);
|
||||
|
||||
int frameSize;
|
||||
if (isLast)
|
||||
frameSize = count - (i * SendChunkSize);
|
||||
else
|
||||
frameSize = SendChunkSize;
|
||||
for (int i = 0; i < frameCount; i++, index += SendChunkSize)
|
||||
{
|
||||
bool isLast = i == (frameCount - 1);
|
||||
|
||||
try
|
||||
{
|
||||
await _client.SendAsync(new ArraySegment<byte>(data, index, count), isText ? WebSocketMessageType.Text : WebSocketMessageType.Binary, isLast, _cancelToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT)
|
||||
{
|
||||
return;
|
||||
int frameSize;
|
||||
if (isLast)
|
||||
frameSize = count - (i * SendChunkSize);
|
||||
else
|
||||
frameSize = SendChunkSize;
|
||||
|
||||
try
|
||||
{
|
||||
var type = isText ? WebSocketMessageType.Text : WebSocketMessageType.Binary;
|
||||
await _client.SendAsync(new ArraySegment<byte>(data, index, count), type, isLast, _cancelToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sendLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Check this code
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Discord
|
||||
{
|
||||
CachedMessage msg;
|
||||
if (_messages.TryGetValue(x, out msg))
|
||||
return msg as CachedMessage;
|
||||
return msg;
|
||||
return null;
|
||||
})
|
||||
.Where(x => x != null)
|
||||
|
||||
Reference in New Issue
Block a user