Browse Source

Added support for more events, added benchmark

tags/1.0-rc
RogueException 9 years ago
parent
commit
c64bdb83b4
27 changed files with 678 additions and 502 deletions
  1. +2
    -2
      src/Discord.Net/API/Common/GuildMember.cs
  2. +2
    -2
      src/Discord.Net/API/Common/Message.cs
  3. +2
    -0
      src/Discord.Net/API/Common/Presence.cs
  4. +3
    -1
      src/Discord.Net/API/Common/VoiceState.cs
  5. +10
    -0
      src/Discord.Net/API/Gateway/GuildMemberAddEvent.cs
  6. +12
    -0
      src/Discord.Net/API/Gateway/GuildMemberRemoveEvent.cs
  7. +10
    -0
      src/Discord.Net/API/Gateway/GuildMemberUpdateEvent.cs
  8. +1
    -1
      src/Discord.Net/API/Gateway/GuildRoleCreateEvent.cs
  9. +1
    -1
      src/Discord.Net/API/Gateway/GuildRoleUpdateEvent.cs
  10. +437
    -395
      src/Discord.Net/DiscordSocketClient.cs
  11. +2
    -0
      src/Discord.Net/Entities/Roles/Role.cs
  12. +19
    -5
      src/Discord.Net/Entities/Users/GuildUser.cs
  13. +1
    -3
      src/Discord.Net/Entities/Users/IGuildUser.cs
  14. +16
    -0
      src/Discord.Net/Entities/Users/IVoiceState.cs
  15. +1
    -1
      src/Discord.Net/Entities/Users/SelfUser.cs
  16. +1
    -1
      src/Discord.Net/Entities/Users/User.cs
  17. +5
    -5
      src/Discord.Net/Entities/WebSocket/CachedDMChannel.cs
  18. +60
    -8
      src/Discord.Net/Entities/WebSocket/CachedGuild.cs
  19. +14
    -3
      src/Discord.Net/Entities/WebSocket/CachedGuildUser.cs
  20. +17
    -1
      src/Discord.Net/Entities/WebSocket/CachedPublicUser.cs
  21. +2
    -1
      src/Discord.Net/Entities/WebSocket/CachedSelfUser.cs
  22. +7
    -7
      src/Discord.Net/Entities/WebSocket/CachedTextChannel.cs
  23. +3
    -3
      src/Discord.Net/Entities/WebSocket/ICachedMessageChannel.cs
  24. +7
    -0
      src/Discord.Net/Entities/WebSocket/ICachedUser.cs
  25. +0
    -62
      src/Discord.Net/Entities/WebSocket/IVoiceState.cs.old
  26. +42
    -0
      src/Discord.Net/Entities/WebSocket/VoiceState.cs
  27. +1
    -0
      src/Discord.Net/project.json

+ 2
- 2
src/Discord.Net/API/Common/GuildMember.cs View File

@@ -14,8 +14,8 @@ namespace Discord.API
[JsonProperty("joined_at")]
public DateTime?JoinedAt { get; set; }
[JsonProperty("deaf")]
public bool Deaf { get; set; }
public bool? Deaf { get; set; }
[JsonProperty("mute")]
public bool Mute { get; set; }
public bool? Mute { get; set; }
}
}

+ 2
- 2
src/Discord.Net/API/Common/Message.cs View File

@@ -27,7 +27,7 @@ namespace Discord.API
public Attachment[] Attachments { get; set; }
[JsonProperty("embeds")]
public Embed[] Embeds { get; set; }
[JsonProperty("nonce")]
public uint? Nonce { get; set; }
/*[JsonProperty("nonce")]
public object Nonce { get; set; }*/
}
}

+ 2
- 0
src/Discord.Net/API/Common/Presence.cs View File

@@ -6,6 +6,8 @@ namespace Discord.API
{
[JsonProperty("user")]
public User User { get; set; }
[JsonProperty("guild_id")]
public ulong? GuildId { get; set; }
[JsonProperty("status")]
public UserStatus Status { get; set; }
[JsonProperty("game")]


+ 3
- 1
src/Discord.Net/API/Common/VoiceState.cs View File

@@ -4,8 +4,10 @@ namespace Discord.API
{
public class VoiceState
{
[JsonProperty("guild_id")]
public ulong? GuildId { get; set; }
[JsonProperty("channel_id")]
public ulong ChannelId { get; set; }
public ulong? ChannelId { get; set; }
[JsonProperty("user_id")]
public ulong UserId { get; set; }
[JsonProperty("session_id")]


+ 10
- 0
src/Discord.Net/API/Gateway/GuildMemberAddEvent.cs View File

@@ -0,0 +1,10 @@
using Newtonsoft.Json;

namespace Discord.API.Gateway
{
public class GuildMemberAddEvent : GuildMember
{
[JsonProperty("guild_id")]
public ulong GuildId { get; set; }
}
}

+ 12
- 0
src/Discord.Net/API/Gateway/GuildMemberRemoveEvent.cs View File

@@ -0,0 +1,12 @@
using Newtonsoft.Json;

namespace Discord.API.Gateway
{
public class GuildMemberRemoveEvent
{
[JsonProperty("guild_id")]
public ulong GuildId { get; set; }
[JsonProperty("user")]
public User User { get; set; }
}
}

+ 10
- 0
src/Discord.Net/API/Gateway/GuildMemberUpdateEvent.cs View File

@@ -0,0 +1,10 @@
using Newtonsoft.Json;

namespace Discord.API.Gateway
{
public class GuildMemberUpdateEvent : GuildMember
{
[JsonProperty("guild_id")]
public ulong GuildId { get; set; }
}
}

+ 1
- 1
src/Discord.Net/API/Gateway/GuildRoleCreateEvent.cs View File

@@ -7,6 +7,6 @@ namespace Discord.API.Gateway
[JsonProperty("guild_id")]
public ulong GuildId { get; set; }
[JsonProperty("role")]
public Role Data { get; set; }
public Role Role { get; set; }
}
}

+ 1
- 1
src/Discord.Net/API/Gateway/GuildRoleUpdateEvent.cs View File

@@ -7,6 +7,6 @@ namespace Discord.API.Gateway
[JsonProperty("guild_id")]
public ulong GuildId { get; set; }
[JsonProperty("role")]
public Role Data { get; set; }
public Role Role { get; set; }
}
}

+ 437
- 395
src/Discord.Net/DiscordSocketClient.cs View File

@@ -41,6 +41,9 @@ namespace Discord

private readonly ConcurrentQueue<ulong> _largeGuilds;
private readonly Logger _gatewayLogger;
#if BENCHMARK
private readonly Logger _benchmarkLogger;
#endif
private readonly DataStoreProvider _dataStoreProvider;
private readonly JsonSerializer _serializer;
private readonly int _connectionTimeout, _reconnectDelay, _failedReconnectDelay;
@@ -106,7 +109,10 @@ namespace Discord
_largeThreshold = config.LargeThreshold;
_gatewayLogger = _log.CreateLogger("Gateway");
#if BENCHMARK
_benchmarkLogger = _log.CreateLogger("Benchmark");
#endif

_serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() };
ApiClient.SentGatewayMessage += async opCode => await _gatewayLogger.Debug($"Sent {(GatewayOpCode)opCode}");
@@ -207,7 +213,7 @@ namespace Discord
{
dataStore = dataStore ?? DataStore;

var guild = new CachedGuild(this, model);
var guild = new CachedGuild(this, model, dataStore);
if (model.Unavailable != true)
{
for (int i = 0; i < model.Channels.Length; i++)
@@ -247,7 +253,7 @@ namespace Discord
{
dataStore = dataStore ?? DataStore;

var recipient = AddCachedUser(model.Recipient, dataStore);
var recipient = GetOrAddCachedUser(model.Recipient, dataStore);
var channel = recipient.AddDMChannel(model);
dataStore.AddChannel(channel);
return channel;
@@ -287,7 +293,7 @@ namespace Discord
{
return Task.FromResult<IUser>(DataStore.Users.Where(x => x.Discriminator == discriminator && x.Username == username).FirstOrDefault());
}
internal CachedPublicUser AddCachedUser(API.User model, DataStore dataStore = null)
internal CachedPublicUser GetOrAddCachedUser(API.User model, DataStore dataStore = null)
{
dataStore = dataStore ?? DataStore;

@@ -299,8 +305,7 @@ namespace Discord
{
dataStore = dataStore ?? DataStore;

var user = dataStore.GetUser(id);
user.RemoveRef();
var user = dataStore.RemoveUser(id);
return user;
}

@@ -336,7 +341,7 @@ namespace Discord
batchTasks[j] = guild.DownloaderPromise;
}

ApiClient.SendRequestMembers(batchIds);
await ApiClient.SendRequestMembers(batchIds).ConfigureAwait(false);

if (isLast && batchCount > 1)
await Task.WhenAll(batchTasks.Take(count)).ConfigureAwait(false);
@@ -347,474 +352,511 @@ namespace Discord

private async Task ProcessMessage(GatewayOpCode opCode, int? seq, string type, object payload)
{
if (seq != null)
_lastSeq = seq.Value;
#if BENCHMARK
Stopwatch stopwatch = Stopwatch.StartNew();
try
{
switch (opCode)
#endif
if (seq != null)
_lastSeq = seq.Value;
try
{
case GatewayOpCode.Hello:
{
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:
switch (type)
{
//Global
case "READY":
{
//TODO: Make downloading large guilds optional
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);

for (int i = 0; i < data.Guilds.Length; i++)
AddCachedGuild(data.Guilds[i], dataStore);
for (int i = 0; i < data.PrivateChannels.Length; i++)
AddCachedDMChannel(data.PrivateChannels[i], dataStore);

_sessionId = data.SessionId;
DataStore = dataStore;

await Ready.Raise().ConfigureAwait(false);

_connectTask.TrySetResult(true); //Signal the .Connect() call to complete
}
break;

//Guilds
case "GUILD_CREATE":
{
var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer);
var guild = new CachedGuild(this, data);
DataStore.AddGuild(guild);

if (data.Unavailable == false)
type = "GUILD_AVAILABLE";
else
await JoinedGuild.Raise(guild).ConfigureAwait(false);
await GuildAvailable.Raise(guild);
}
break;
case "GUILD_UPDATE":
{
var data = (payload as JToken).ToObject<API.Guild>(_serializer);
var guild = DataStore.GetGuild(data.Id);
if (guild != null)
switch (opCode)
{
case GatewayOpCode.Hello:
{
await _gatewayLogger.Debug($"Received Hello").ConfigureAwait(false);
var data = (payload as JToken).ToObject<HelloEvent>(_serializer);

await ApiClient.SendIdentify().ConfigureAwait(false);
_heartbeatTask = RunHeartbeat(data.HeartbeatInterval, _heartbeatCancelToken.Token);
}
break;
case GatewayOpCode.HeartbeatAck:
{
await _gatewayLogger.Debug($"Received HeartbeatAck").ConfigureAwait(false);

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:
switch (type)
{
//Global
case "READY":
{
var before = _enablePreUpdateEvents ? guild.Clone() : null;
guild.Update(data, UpdateSource.WebSocket);
await GuildUpdated.Raise(before, guild);
await _gatewayLogger.Debug($"Received Dispatch (READY)").ConfigureAwait(false);

//TODO: Make downloading large guilds optional
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);

for (int i = 0; i < data.Guilds.Length; i++)
AddCachedGuild(data.Guilds[i], dataStore);
for (int i = 0; i < data.PrivateChannels.Length; i++)
AddCachedDMChannel(data.PrivateChannels[i], dataStore);

_sessionId = data.SessionId;
DataStore = dataStore;

await Ready.Raise().ConfigureAwait(false);

_connectTask.TrySetResult(true); //Signal the .Connect() call to complete
}
else
await _gatewayLogger.Warning("GUILD_UPDATE referenced an unknown guild.");
}
break;
case "GUILD_DELETE":
{
var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer);
var guild = DataStore.RemoveGuild(data.Id);
if (guild != null)
break;

//Guilds
case "GUILD_CREATE":
{
if (data.Unavailable == true)
type = "GUILD_UNAVAILABLE";
var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer);
var guild = new CachedGuild(this, data, DataStore);
DataStore.AddGuild(guild);

if (data.Unavailable == false)
type = "GUILD_AVAILABLE";
await _gatewayLogger.Debug($"Received Dispatch ({type})").ConfigureAwait(false);

await GuildUnavailable.Raise(guild);
if (data.Unavailable != true)
await LeftGuild.Raise(guild);
if (data.Unavailable != false)
await JoinedGuild.Raise(guild).ConfigureAwait(false);

await GuildAvailable.Raise(guild).ConfigureAwait(false);
}
else
await _gatewayLogger.Warning("GUILD_DELETE referenced an unknown guild.");
}
break;

//Channels
case "CHANNEL_CREATE":
{
var data = (payload as JToken).ToObject<API.Channel>(_serializer);

ICachedChannel channel = null;
if (data.GuildId != null)
break;
case "GUILD_UPDATE":
{
var guild = DataStore.GetGuild(data.GuildId.Value);
await _gatewayLogger.Debug($"Received Dispatch (GUILD_UPDATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Guild>(_serializer);
var guild = DataStore.GetGuild(data.Id);
if (guild != null)
{
channel = guild.AddCachedChannel(data);
DataStore.AddChannel(channel);
var before = _enablePreUpdateEvents ? guild.Clone() : null;
guild.Update(data, UpdateSource.WebSocket);
await GuildUpdated.Raise(before, guild).ConfigureAwait(false);
}
else
await _gatewayLogger.Warning("CHANNEL_CREATE referenced an unknown guild.");
await _gatewayLogger.Warning("GUILD_UPDATE referenced an unknown guild.");
}
else
channel = AddCachedDMChannel(data);
if (channel != null)
await ChannelCreated.Raise(channel);
}
break;
case "CHANNEL_UPDATE":
{
var data = (payload as JToken).ToObject<API.Channel>(_serializer);
var channel = DataStore.GetChannel(data.Id);
if (channel != null)
break;
case "GUILD_DELETE":
{
var before = _enablePreUpdateEvents ? channel.Clone() : null;
channel.Update(data, UpdateSource.WebSocket);
await ChannelUpdated.Raise(before, channel);
var data = (payload as JToken).ToObject<ExtendedGuild>(_serializer);
if (data.Unavailable == true)
type = "GUILD_UNAVAILABLE";
await _gatewayLogger.Debug($"Received Dispatch ({type})").ConfigureAwait(false);

var guild = DataStore.RemoveGuild(data.Id);
if (guild != null)
{
await GuildUnavailable.Raise(guild).ConfigureAwait(false);
if (data.Unavailable != true)
await LeftGuild.Raise(guild).ConfigureAwait(false);
foreach (var member in guild.Members)
member.User.RemoveRef();
}
else
await _gatewayLogger.Warning($"{type} referenced an unknown guild.").ConfigureAwait(false);
}
else
await _gatewayLogger.Warning("CHANNEL_UPDATE referenced an unknown channel.");
}
break;
case "CHANNEL_DELETE":
{
var data = (payload as JToken).ToObject<API.Channel>(_serializer);
var channel = RemoveCachedChannel(data.Id);
if (channel != null)
await ChannelDestroyed.Raise(channel);
else
await _gatewayLogger.Warning("CHANNEL_DELETE referenced an unknown channel.");
}
break;

//Members
/*case "GUILD_MEMBER_ADD":
{
var data = (payload as JToken).ToObject<API.GuildMember>(_serializer);
var guild = GetGuild(data.GuildId.Value);
if (guild != null)
break;

//Channels
case "CHANNEL_CREATE":
{
var user = guild.AddCachedUser(data.User.Id, true, true);
user.Update(data);
user.UpdateActivity();
UserJoined.Raise(user);
await _gatewayLogger.Debug($"Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Channel>(_serializer);
ICachedChannel channel = null;
if (data.GuildId != null)
{
var guild = DataStore.GetGuild(data.GuildId.Value);
if (guild != null)
{
channel = guild.AddCachedChannel(data);
DataStore.AddChannel(channel);
}
else
await _gatewayLogger.Warning("CHANNEL_CREATE referenced an unknown guild.");
}
else
channel = AddCachedDMChannel(data);
if (channel != null)
await ChannelCreated.Raise(channel);
}
else
await _gatewayLogger.Warning("GUILD_MEMBER_ADD referenced an unknown guild.");
}
break;
case "GUILD_MEMBER_UPDATE":
{
var data = (payload as JToken).ToObject<API.GuildMember>(_serializer);
var guild = GetGuild(data.GuildId.Value);
if (guild != null)
break;
case "CHANNEL_UPDATE":
{
var user = guild.GetCachedUser(data.User.Id);
if (user != null)
await _gatewayLogger.Debug($"Received Dispatch (CHANNEL_UPDATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Channel>(_serializer);
var channel = DataStore.GetChannel(data.Id);
if (channel != null)
{
var before = _enablePreUpdateEvents ? user.Clone() : null;
user.Update(data);
await UserUpdated.Raise(before, user);
var before = _enablePreUpdateEvents ? channel.Clone() : null;
channel.Update(data, UpdateSource.WebSocket);
await ChannelUpdated.Raise(before, channel);
}
else
await _gatewayLogger.Warning("GUILD_MEMBER_UPDATE referenced an unknown user.");
await _gatewayLogger.Warning("CHANNEL_UPDATE referenced an unknown channel.");
}
break;
case "CHANNEL_DELETE":
{
await _gatewayLogger.Debug($"Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Channel>(_serializer);
var channel = RemoveCachedChannel(data.Id);
if (channel != null)
await ChannelDestroyed.Raise(channel);
else
await _gatewayLogger.Warning("CHANNEL_DELETE referenced an unknown channel.");
}
else
await _gatewayLogger.Warning("GUILD_MEMBER_UPDATE referenced an unknown guild.");
}
break;
case "GUILD_MEMBER_REMOVE":
{
var data = (payload as JToken).ToObject<API.GuildMember>(_serializer);
var guild = GetGuild(data.GuildId.Value);
if (guild != null)
break;

//Members
case "GUILD_MEMBER_ADD":
{
var user = guild.RemoveCachedUser(data.User.Id);
if (user != null)
await _gatewayLogger.Debug($"Received Dispatch (GUILD_MEMBER_ADD)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<GuildMemberAddEvent>(_serializer);
var guild = DataStore.GetGuild(data.GuildId);
if (guild != null)
{
user.GlobalUser.RemoveGuild();
if (user.GuildCount == 0 && user.DMChannel == null)
DataStore.RemoveUser(user.Id);
await UserLeft.Raise(user);
var user = guild.AddCachedUser(data);
await UserJoined.Raise(user).ConfigureAwait(false);
}
else
await _gatewayLogger.Warning("GUILD_MEMBER_REMOVE referenced an unknown user.");
await _gatewayLogger.Warning("GUILD_MEMBER_ADD referenced an unknown guild.");
}
else
await _gatewayLogger.Warning("GUILD_MEMBER_REMOVE referenced an unknown guild.");
}
break;
case "GUILD_MEMBERS_CHUNK":
{
var data = (payload as JToken).ToObject<GuildMembersChunkEvent>(_serializer);
var guild = GetCachedGuild(data.GuildId);
if (guild != null)
break;
case "GUILD_MEMBER_UPDATE":
{
foreach (var memberData in data.Members)
await _gatewayLogger.Debug($"Received Dispatch (GUILD_MEMBER_UPDATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<GuildMemberUpdateEvent>(_serializer);
var guild = DataStore.GetGuild(data.GuildId);
if (guild != null)
{
var user = guild.AddCachedUser(memberData.User.Id, true, false);
user.Update(memberData);
var user = guild.GetCachedUser(data.User.Id);
if (user != null)
{
var before = _enablePreUpdateEvents ? user.Clone() : null;
user.Update(data, UpdateSource.WebSocket);
await UserUpdated.Raise(before, user);
}
else
await _gatewayLogger.Warning("GUILD_MEMBER_UPDATE referenced an unknown user.");
}

if (guild.CurrentUserCount >= guild.UserCount) //Finished downloading for there
await GuildAvailable.Raise(guild);
else
await _gatewayLogger.Warning("GUILD_MEMBER_UPDATE referenced an unknown guild.");
}
else
await _gatewayLogger.Warning("GUILD_MEMBERS_CHUNK referenced an unknown guild.");
}
break;

//Roles
/*case "GUILD_ROLE_CREATE":
{
var data = (payload as JToken).ToObject<GuildRoleCreateEvent>(_serializer);
var guild = GetCachedGuild(data.GuildId);
if (guild != null)
break;
case "GUILD_MEMBER_REMOVE":
{
var role = guild.AddCachedRole(data.Data.Id);
role.Update(data.Data, false);
RoleCreated.Raise(role);
await _gatewayLogger.Debug($"Received Dispatch (GUILD_MEMBER_REMOVE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<GuildMemberRemoveEvent>(_serializer);
var guild = DataStore.GetGuild(data.GuildId);
if (guild != null)
{
var user = guild.RemoveCachedUser(data.User.Id);
if (user != null)
{
user.User.RemoveRef();
await UserLeft.Raise(user);
}
else
await _gatewayLogger.Warning("GUILD_MEMBER_REMOVE referenced an unknown user.");
}
else
await _gatewayLogger.Warning("GUILD_MEMBER_REMOVE referenced an unknown guild.");
}
else
await _gatewayLogger.Warning("GUILD_ROLE_CREATE referenced an unknown guild.");
}
break;
case "GUILD_ROLE_UPDATE":
{
var data = (payload as JToken).ToObject<GuildRoleUpdateEvent>(_serializer);
var guild = GetCachedGuild(data.GuildId);
if (guild != null)
break;
case "GUILD_MEMBERS_CHUNK":
{
var role = guild.GetRole(data.Data.Id);
if (role != null)
await _gatewayLogger.Debug($"Received Dispatch (GUILD_MEMBERS_CHUNK)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<GuildMembersChunkEvent>(_serializer);
var guild = DataStore.GetGuild(data.GuildId);
if (guild != null)
{
var before = _enablePreUpdateEvents ? role.Clone() : null;
role.Update(data.Data, true);
RoleUpdated.Raise(before, role);
foreach (var memberModel in data.Members)
guild.AddCachedUser(memberModel);

if (guild.DownloadedMemberCount >= guild.MemberCount) //Finished downloading for there
{
guild.CompleteDownloadMembers();
await GuildDownloadedMembers.Raise(guild).ConfigureAwait(false);
}
}
else
await _gatewayLogger.Warning("GUILD_ROLE_UPDATE referenced an unknown role.");
await _gatewayLogger.Warning("GUILD_MEMBERS_CHUNK referenced an unknown guild.");
}
else
await _gatewayLogger.Warning("GUILD_ROLE_UPDATE referenced an unknown guild.");
}
break;
case "GUILD_ROLE_DELETE":
{
var data = (payload as JToken).ToObject<GuildRoleDeleteEvent>(_serializer);
var guild = DataStore.GetGuild(data.GuildId);
if (guild != null)
break;

//Roles
case "GUILD_ROLE_CREATE":
{
var role = guild.RemoveRole(data.RoleId);
if (role != null)
RoleDeleted.Raise(role);
await _gatewayLogger.Debug($"Received Dispatch (GUILD_ROLE_CREATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<GuildRoleCreateEvent>(_serializer);
var guild = DataStore.GetGuild(data.GuildId);
if (guild != null)
{
var role = guild.AddCachedRole(data.Role);
await RoleCreated.Raise(role).ConfigureAwait(false);
}
else
await _gatewayLogger.Warning("GUILD_ROLE_DELETE referenced an unknown role.");
await _gatewayLogger.Warning("GUILD_ROLE_CREATE referenced an unknown guild.");
}
else
await _gatewayLogger.Warning("GUILD_ROLE_DELETE referenced an unknown guild.");
}
break;

//Bans
case "GUILD_BAN_ADD":
{
var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer);
var guild = GetCachedGuild(data.GuildId);
if (guild != null)
await UserBanned.Raise(new User(this, data));
else
await _gatewayLogger.Warning("GUILD_BAN_ADD referenced an unknown guild.");
}
break;
case "GUILD_BAN_REMOVE":
{
var data = payload.ToObject<GuildBanEvent>(_serializer);
var guild = GetCachedGuild(data.GuildId);
if (guild != null)
await UserUnbanned.Raise(new User(this, data));
else
await _gatewayLogger.Warning("GUILD_BAN_REMOVE referenced an unknown guild.");
}
break;

//Messages
case "MESSAGE_CREATE":
{
var data = (payload as JToken).ToObject<API.Message>(_serializer);
var channel = DataStore.GetChannel(data.ChannelId);
if (channel != null)
break;
case "GUILD_ROLE_UPDATE":
{
var user = channel.GetUser(data.Author.Id);
await _gatewayLogger.Debug($"Received Dispatch (GUILD_ROLE_UPDATE)").ConfigureAwait(false);

if (user != null)
var data = (payload as JToken).ToObject<GuildRoleUpdateEvent>(_serializer);
var guild = DataStore.GetGuild(data.GuildId);
if (guild != null)
{
bool isAuthor = data.Author.Id == CurrentUser.Id;
var msg = channel.AddMessage(data.Id, user, data.Timestamp.Value);

msg.Update(data);

MessageReceived.Raise(msg);
var role = guild.GetRole(data.Role.Id);
if (role != null)
{
var before = _enablePreUpdateEvents ? role.Clone() : null;
role.Update(data.Role, UpdateSource.WebSocket);
await RoleUpdated.Raise(before, role).ConfigureAwait(false);
}
else
await _gatewayLogger.Warning("GUILD_ROLE_UPDATE referenced an unknown role.");
}
else
await _gatewayLogger.Warning("MESSAGE_CREATE referenced an unknown user.");
await _gatewayLogger.Warning("GUILD_ROLE_UPDATE referenced an unknown guild.");
}
else
await _gatewayLogger.Warning("MESSAGE_CREATE referenced an unknown channel.");
}
break;
case "MESSAGE_UPDATE":
{
var data = (payload as JToken).ToObject<API.Message>(_serializer);
var channel = GetCachedChannel(data.ChannelId);
if (channel != null)
break;
case "GUILD_ROLE_DELETE":
{
var msg = channel.GetMessage(data.Id, data.Author?.Id);
var before = _enablePreUpdateEvents ? msg.Clone() : null;
msg.Update(data);
MessageUpdated.Raise(before, msg);
await _gatewayLogger.Debug($"Received Dispatch (GUILD_ROLE_DELETE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<GuildRoleDeleteEvent>(_serializer);
var guild = DataStore.GetGuild(data.GuildId);
if (guild != null)
{
var role = guild.RemoveCachedRole(data.RoleId);
if (role != null)
await RoleDeleted.Raise(role).ConfigureAwait(false);
else
await _gatewayLogger.Warning("GUILD_ROLE_DELETE referenced an unknown role.");
}
else
await _gatewayLogger.Warning("GUILD_ROLE_DELETE referenced an unknown guild.");
}
else
await _gatewayLogger.Warning("MESSAGE_UPDATE referenced an unknown channel.");
}
break;
case "MESSAGE_DELETE":
{
var data = (payload as JToken).ToObject<API.Message>(_serializer);
var channel = GetCachedChannel(data.ChannelId);
if (channel != null)
break;

//Bans
case "GUILD_BAN_ADD":
{
var msg = channel.RemoveMessage(data.Id);
MessageDeleted.Raise(msg);
await _gatewayLogger.Debug($"Received Dispatch (GUILD_BAN_ADD)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer);
var guild = DataStore.GetGuild(data.GuildId);
if (guild != null)
await UserBanned.Raise(new User(this, data));
else
await _gatewayLogger.Warning("GUILD_BAN_ADD referenced an unknown guild.");
}
else
await _gatewayLogger.Warning("MESSAGE_DELETE referenced an unknown channel.");
}
break;

//Statuses
case "PRESENCE_UPDATE":
{
var data = (payload as JToken).ToObject<API.Presence>(_serializer);
User user;
Guild guild;
if (data.GuildId == null)
break;
case "GUILD_BAN_REMOVE":
{
guild = null;
user = GetPrivateChannel(data.User.Id)?.Recipient;
await _gatewayLogger.Debug($"Received Dispatch (GUILD_BAN_REMOVE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<GuildBanEvent>(_serializer);
var guild = DataStore.GetGuild(data.GuildId);
if (guild != null)
await UserUnbanned.Raise(new User(this, data));
else
await _gatewayLogger.Warning("GUILD_BAN_REMOVE referenced an unknown guild.");
}
else
break;

//Messages
case "MESSAGE_CREATE":
{
guild = GetGuild(data.GuildId.Value);
if (guild == null)
await _gatewayLogger.Debug($"Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Message>(_serializer);
var channel = DataStore.GetChannel(data.ChannelId) as ICachedMessageChannel;
if (channel != null)
{
await _gatewayLogger.Warning("PRESENCE_UPDATE referenced an unknown guild.");
break;
var author = channel.GetCachedUser(data.Author.Id);

if (author != null)
{
var msg = channel.AddCachedMessage(author, data);
await MessageReceived.Raise(msg).ConfigureAwait(false);
}
else
await _gatewayLogger.Warning("MESSAGE_CREATE referenced an unknown user.");
}
else
user = guild.GetUser(data.User.Id);
await _gatewayLogger.Warning("MESSAGE_CREATE referenced an unknown channel.");
}

if (user != null)
break;
case "MESSAGE_UPDATE":
{
var before = _enablePreUpdateEvents ? user.Clone() : null;
user.Update(data);
UserUpdated.Raise(before, user);
await _gatewayLogger.Debug($"Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Message>(_serializer);
var channel = DataStore.GetChannel(data.ChannelId) as ICachedMessageChannel;
if (channel != null)
{
var msg = channel.GetCachedMessage(data.Id);
var before = _enablePreUpdateEvents ? msg.Clone() : null;
msg.Update(data, UpdateSource.WebSocket);
await MessageUpdated.Raise(before, msg).ConfigureAwait(false);
}
else
await _gatewayLogger.Warning("MESSAGE_UPDATE referenced an unknown channel.");
}
else
break;
case "MESSAGE_DELETE":
{
//Occurs when a user leaves a guild
//await _gatewayLogger.Warning("PRESENCE_UPDATE referenced an unknown user.");
await _gatewayLogger.Debug($"Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Message>(_serializer);
var channel = DataStore.GetChannel(data.ChannelId) as ICachedMessageChannel;
if (channel != null)
{
var msg = channel.RemoveCachedMessage(data.Id);
await MessageDeleted.Raise(msg).ConfigureAwait(false);
}
else
await _gatewayLogger.Warning("MESSAGE_DELETE referenced an unknown channel.");
}
}
break;
case "TYPING_START":
{
var data = (payload as JToken).ToObject<TypingStartEvent>(_serializer);
var channel = GetCachedChannel(data.ChannelId);
if (channel != null)
break;

//Statuses
case "PRESENCE_UPDATE":
{
var user = channel.GetUser(data.UserId);
if (user != null)
await _gatewayLogger.Debug($"Received Dispatch (PRESENCE_UPDATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.Presence>(_serializer);
if (data.GuildId == null)
{
var user = DataStore.GetUser(data.User.Id);
if (user == null)
user.Update(data, UpdateSource.WebSocket);
}
else
{
await UserIsTyping.Raise(channel, user);
user.UpdateActivity();
var guild = DataStore.GetGuild(data.GuildId.Value);
if (guild == null)
{
await _gatewayLogger.Warning("PRESENCE_UPDATE referenced an unknown guild.");
break;
}
if (data.Status == UserStatus.Offline)
guild.RemoveCachedPresence(data.User.Id);
else
guild.AddOrUpdateCachedPresence(data);
}
}
else
await _gatewayLogger.Warning("TYPING_START referenced an unknown channel.");
}
break;

//Voice
case "VOICE_STATE_UPDATE":
{
var data = (payload as JToken).ToObject<API.VoiceState>(_serializer);
var guild = GetGuild(data.GuildId);
if (guild != null)
break;
case "TYPING_START":
{
var user = guild.GetUser(data.UserId);
if (user != null)
await _gatewayLogger.Debug($"Received Dispatch (TYPING_START)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<TypingStartEvent>(_serializer);
var channel = DataStore.GetChannel(data.ChannelId) as ICachedMessageChannel;
if (channel != null)
{
var before = _enablePreUpdateEvents ? user.Clone() : null;
user.Update(data);
UserUpdated.Raise(before, user);
var user = channel.GetCachedUser(data.UserId);
if (user != null)
await UserIsTyping.Raise(channel, user).ConfigureAwait(false);
}
else
await _gatewayLogger.Warning("TYPING_START referenced an unknown channel.").ConfigureAwait(false);
}
break;

//Voice
case "VOICE_STATE_UPDATE":
{
await _gatewayLogger.Debug($"Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.VoiceState>(_serializer);
if (data.GuildId.HasValue)
{
//Occurs when a user leaves a guild
//await _gatewayLogger.Warning("VOICE_STATE_UPDATE referenced an unknown user.");
var guild = DataStore.GetGuild(data.GuildId.Value);
if (guild != null)
{
if (data.ChannelId == null)
guild.RemoveCachedVoiceState(data.UserId);
else
guild.AddOrUpdateCachedVoiceState(data);

var user = guild.GetCachedUser(data.UserId);
user.Update(data, UpdateSource.WebSocket);
}
else
await _gatewayLogger.Warning("VOICE_STATE_UPDATE referenced an unknown guild.").ConfigureAwait(false);
}
}
else
await _gatewayLogger.Warning("VOICE_STATE_UPDATE referenced an unknown guild.");
}
break;

//Settings
case "USER_UPDATE":
{
var data = (payload as JToken).ToObject<SelfUser>(_serializer);
if (data.Id == CurrentUser.Id)
break;

//Settings
case "USER_UPDATE":
{
var before = _enablePreUpdateEvents ? CurrentUser.Clone() : null;
CurrentUser.Update(data);
await CurrentUserUpdated.Raise(before, CurrentUser).ConfigureAwait(false);
await _gatewayLogger.Debug($"Received Dispatch (USER_UPDATE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<API.User>(_serializer);
if (data.Id == CurrentUser.Id)
{
var before = _enablePreUpdateEvents ? CurrentUser.Clone() : null;
CurrentUser.Update(data, UpdateSource.WebSocket);
await CurrentUserUpdated.Raise(before, CurrentUser).ConfigureAwait(false);
}
}
}
break;*/

//Ignored
case "USER_SETTINGS_UPDATE":
case "MESSAGE_ACK": //TODO: Add (User only)
case "GUILD_EMOJIS_UPDATE": //TODO: Add
case "GUILD_INTEGRATIONS_UPDATE": //TODO: Add
case "VOICE_SERVER_UPDATE": //TODO: Add
case "RESUMED": //TODO: Add
await _gatewayLogger.Debug($"Ignored Dispatch ({type})").ConfigureAwait(false);
return;
//Others
default:
await _gatewayLogger.Warning($"Unknown Dispatch ({type})").ConfigureAwait(false);
return;
}
break;
default:
await _gatewayLogger.Warning($"Unknown OpCode ({opCode})").ConfigureAwait(false);
return;
break;
//Ignored
case "USER_SETTINGS_UPDATE":
case "MESSAGE_ACK": //TODO: Add (User only)
case "GUILD_EMOJIS_UPDATE": //TODO: Add
case "GUILD_INTEGRATIONS_UPDATE": //TODO: Add
case "VOICE_SERVER_UPDATE": //TODO: Add
case "RESUMED": //TODO: Add
await _gatewayLogger.Debug($"Ignored Dispatch ({type})").ConfigureAwait(false);
return;
//Others
default:
await _gatewayLogger.Warning($"Unknown Dispatch ({type})").ConfigureAwait(false);
return;
}
break;
default:
await _gatewayLogger.Warning($"Unknown OpCode ({opCode})").ConfigureAwait(false);
return;
}
}
catch (Exception ex)
{
await _gatewayLogger.Error($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false);
return;
}
#if BENCHMARK
}
catch (Exception ex)
finally
{
await _gatewayLogger.Error($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false);
return;
stopwatch.Stop();
double millis = Math.Round(stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2);
await _benchmarkLogger.Debug($"{millis} ms").ConfigureAwait(false);
}
await _gatewayLogger.Debug($"Received {opCode}{(type != null ? $" ({type})" : "")}").ConfigureAwait(false);
#endif
}
private async Task RunHeartbeat(int intervalMillis, CancellationToken cancelToken)
{


+ 2
- 0
src/Discord.Net/Entities/Roles/Role.cs View File

@@ -57,6 +57,8 @@ namespace Discord
{
await Discord.ApiClient.DeleteGuildRole(Guild.Id, Id).ConfigureAwait(false);
}

public Role Clone() => MemberwiseClone() as Role;
public override string ToString() => Name;
private string DebuggerDisplay => $"{Name} ({Id})";


+ 19
- 5
src/Discord.Net/Entities/Users/GuildUser.cs View File

@@ -5,6 +5,7 @@ using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.GuildMember;
using VoiceStateModel = Discord.API.VoiceState;

namespace Discord
{
@@ -24,12 +25,12 @@ namespace Discord
public string AvatarUrl => User.AvatarUrl;
public DateTime CreatedAt => User.CreatedAt;
public string Discriminator => User.Discriminator;
public Game? Game => User.Game;
public bool IsAttached => User.IsAttached;
public bool IsBot => User.IsBot;
public string Mention => User.Mention;
public UserStatus Status => User.Status;
public string Username => User.Username;
public virtual UserStatus Status => User.Status;
public virtual Game? Game => User.Game;

public DiscordClient Discord => Guild.Discord;

@@ -43,8 +44,10 @@ namespace Discord
{
if (source == UpdateSource.Rest && IsAttached) return;

IsDeaf = model.Deaf;
IsMute = model.Mute;
if (model.Deaf.HasValue)
IsDeaf = model.Deaf.Value;
if (model.Mute.HasValue)
IsMute = model.Mute.Value;
JoinedAt = model.JoinedAt.Value;
Nickname = model.Nick;

@@ -56,6 +59,13 @@ namespace Discord

GuildPermissions = new GuildPermissions(Permissions.ResolveGuild(this));
}
public void Update(VoiceStateModel model, UpdateSource source)
{
if (source == UpdateSource.Rest && IsAttached) return;

IsDeaf = model.Deaf;
IsMute = model.Mute;
}

public async Task Update()
{
@@ -107,6 +117,10 @@ namespace Discord

IGuild IGuildUser.Guild => Guild;
IReadOnlyCollection<IRole> IGuildUser.Roles => Roles;
IVoiceChannel IGuildUser.VoiceChannel => null;
bool IVoiceState.IsSelfDeafened => false;
bool IVoiceState.IsSelfMuted => false;
bool IVoiceState.IsSuppressed => false;
IVoiceChannel IVoiceState.VoiceChannel => null;
string IVoiceState.VoiceSessionId => null;
}
}

+ 1
- 3
src/Discord.Net/Entities/Users/IGuildUser.cs View File

@@ -6,7 +6,7 @@ using Discord.API.Rest;
namespace Discord
{
/// <summary> A Guild-User pairing. </summary>
public interface IGuildUser : IUpdateable, IUser
public interface IGuildUser : IUpdateable, IUser, IVoiceState
{
/// <summary> Returns true if the guild has deafened this user. </summary>
bool IsDeaf { get; }
@@ -23,8 +23,6 @@ namespace Discord
IGuild Guild { get; }
/// <summary> Returns a collection of the roles this user is a member of in this guild, including the guild's @everyone role. </summary>
IReadOnlyCollection<IRole> Roles { get; }
/// <summary> Gets the voice channel this user is currently in, if any. </summary>
IVoiceChannel VoiceChannel { get; }

/// <summary> Gets the channel-level permissions granted to this user for a given channel. </summary>
ChannelPermissions GetPermissions(IGuildChannel channel);


+ 16
- 0
src/Discord.Net/Entities/Users/IVoiceState.cs View File

@@ -0,0 +1,16 @@
namespace Discord
{
public interface IVoiceState
{
/// <summary> Returns true if this user has marked themselves as deafened. </summary>
bool IsSelfDeafened { get; }
/// <summary> Returns true if this user has marked themselves as muted. </summary>
bool IsSelfMuted { get; }
/// <summary> Returns true if the guild is temporarily blocking audio to/from this user. </summary>
bool IsSuppressed { get; }
/// <summary> Gets the voice channel this user is currently in, if any. </summary>
IVoiceChannel VoiceChannel { get; }
/// <summary> Gets the unique identifier for this user's voice session. </summary>
string VoiceSessionId { get; }
}
}

+ 1
- 1
src/Discord.Net/Entities/Users/SelfUser.cs View File

@@ -30,7 +30,7 @@ namespace Discord

var model = await Discord.ApiClient.GetCurrentUser().ConfigureAwait(false);
Update(model, UpdateSource.Rest);
}
}
public async Task Modify(Action<ModifyCurrentUserParams> func)
{
if (func != null) throw new NullReferenceException(nameof(func));


+ 1
- 1
src/Discord.Net/Entities/Users/User.cs View File

@@ -17,9 +17,9 @@ namespace Discord
public override DiscordClient Discord { get; }

public string AvatarUrl => API.CDN.GetUserAvatarUrl(Id, _avatarId);
public virtual Game? Game => null;
public string Mention => MentionUtils.Mention(this, false);
public string NicknameMention => MentionUtils.Mention(this, true);
public virtual Game? Game => null;
public virtual UserStatus Status => UserStatus.Unknown;

public User(DiscordClient discord, Model model)


+ 5
- 5
src/Discord.Net/Entities/WebSocket/CachedDMChannel.cs View File

@@ -13,7 +13,7 @@ namespace Discord

public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
public new CachedPublicUser Recipient => base.Recipient as CachedPublicUser;
public IReadOnlyCollection<IUser> Members => ImmutableArray.Create<IUser>(Discord.CurrentUser, Recipient);
public IReadOnlyCollection<ICachedUser> Members => ImmutableArray.Create<ICachedUser>(Discord.CurrentUser, Recipient);

public CachedDMChannel(DiscordSocketClient discord, CachedPublicUser recipient, Model model)
: base(discord, recipient, model)
@@ -21,11 +21,11 @@ namespace Discord
_messages = new MessageCache(Discord, this);
}

public override Task<IUser> GetUser(ulong id) => Task.FromResult(GetCachedUser(id));
public override Task<IReadOnlyCollection<IUser>> GetUsers() => Task.FromResult(Members);
public override Task<IUser> GetUser(ulong id) => Task.FromResult<IUser>(GetCachedUser(id));
public override Task<IReadOnlyCollection<IUser>> GetUsers() => Task.FromResult<IReadOnlyCollection<IUser>>(Members);
public override Task<IReadOnlyCollection<IUser>> GetUsers(int limit, int offset)
=> Task.FromResult<IReadOnlyCollection<IUser>>(Members.Skip(offset).Take(limit).ToImmutableArray());
public IUser GetCachedUser(ulong id)
public ICachedUser GetCachedUser(ulong id)
{
var currentUser = Discord.CurrentUser;
if (id == Recipient.Id)
@@ -48,7 +48,7 @@ namespace Discord
{
return await _messages.Download(fromMessageId, dir, limit).ConfigureAwait(false);
}
public CachedMessage AddCachedMessage(IUser author, MessageModel model)
public CachedMessage AddCachedMessage(ICachedUser author, MessageModel model)
{
var msg = new CachedMessage(this, author, model);
_messages.Add(msg);


+ 60
- 8
src/Discord.Net/Entities/WebSocket/CachedGuild.cs View File

@@ -11,6 +11,8 @@ using ExtendedModel = Discord.API.Gateway.ExtendedGuild;
using MemberModel = Discord.API.GuildMember;
using Model = Discord.API.Guild;
using PresenceModel = Discord.API.Presence;
using RoleModel = Discord.API.Role;
using VoiceStateModel = Discord.API.VoiceState;

namespace Discord
{
@@ -20,9 +22,11 @@ namespace Discord
private ConcurrentHashSet<ulong> _channels;
private ConcurrentDictionary<ulong, CachedGuildUser> _members;
private ConcurrentDictionary<ulong, Presence> _presences;
private int _userCount;
private ConcurrentDictionary<ulong, VoiceState> _voiceStates;

public bool Available { get; private set; } //TODO: Add to IGuild
public int MemberCount { get; private set; }
public int DownloadedMemberCount { get; private set; }

public bool HasAllMembers => _downloaderPromise.Task.IsCompleted;
public Task DownloaderPromise => _downloaderPromise.Task;
@@ -32,9 +36,10 @@ namespace Discord
public IReadOnlyCollection<ICachedGuildChannel> Channels => _channels.Select(x => GetCachedChannel(x)).ToReadOnlyCollection(_channels);
public IReadOnlyCollection<CachedGuildUser> Members => _members.ToReadOnlyCollection();

public CachedGuild(DiscordSocketClient discord, Model model) : base(discord, model)
public CachedGuild(DiscordSocketClient discord, ExtendedModel model, DataStore dataStore) : base(discord, model)
{
_downloaderPromise = new TaskCompletionSource<bool>();
Update(model, UpdateSource.Creation, dataStore);
}

public void Update(ExtendedModel model, UpdateSource source, DataStore dataStore)
@@ -52,6 +57,8 @@ namespace Discord
_presences = new ConcurrentDictionary<ulong, Presence>();
if (_roles == null)
_roles = new ConcurrentDictionary<ulong, Role>();
if (_voiceStates == null)
_voiceStates = new ConcurrentDictionary<ulong, VoiceState>();
if (Emojis == null)
Emojis = ImmutableArray.Create<Emoji>();
if (Features == null)
@@ -61,7 +68,7 @@ namespace Discord

base.Update(model as Model, source);

_userCount = model.MemberCount;
MemberCount = model.MemberCount;

var channels = new ConcurrentHashSet<ulong>();
if (model.Channels != null)
@@ -75,7 +82,7 @@ namespace Discord
if (model.Presences != null)
{
for (int i = 0; i < model.Presences.Length; i++)
AddCachedPresence(model.Presences[i], presences);
AddOrUpdateCachedPresence(model.Presences[i], presences);
}
_presences = presences;

@@ -85,10 +92,19 @@ namespace Discord
for (int i = 0; i < model.Members.Length; i++)
AddCachedUser(model.Members[i], members, dataStore);
_downloaderPromise = new TaskCompletionSource<bool>();
DownloadedMemberCount = model.Members.Length;
if (!model.Large)
_downloaderPromise.SetResult(true);
}
_members = members;

var voiceStates = new ConcurrentDictionary<ulong, VoiceState>();
if (model.VoiceStates != null)
{
for (int i = 0; i < model.VoiceStates.Length; i++)
AddOrUpdateCachedVoiceState(model.VoiceStates[i], _voiceStates);
}
_voiceStates = voiceStates;
}

public override Task<IGuildChannel> GetChannel(ulong id) => Task.FromResult<IGuildChannel>(GetCachedChannel(id));
@@ -108,7 +124,7 @@ namespace Discord
(channels ?? _channels).TryRemove(id);
}

public Presence AddCachedPresence(PresenceModel model, ConcurrentDictionary<ulong, Presence> presences = null)
public Presence AddOrUpdateCachedPresence(PresenceModel model, ConcurrentDictionary<ulong, Presence> presences = null)
{
var game = model.Game != null ? new Game(model.Game) : (Game?)null;
var presence = new Presence(model.Status, game);
@@ -130,6 +146,42 @@ namespace Discord
return null;
}

public Role AddCachedRole(RoleModel model, ConcurrentDictionary<ulong, Role> roles = null)
{
var role = new Role(this, model);
(roles ?? _roles)[model.Id] = role;
return role;
}
public Role RemoveCachedRole(ulong id)
{
Role role;
if (_roles.TryRemove(id, out role))
return role;
return null;
}

public VoiceState AddOrUpdateCachedVoiceState(VoiceStateModel model, ConcurrentDictionary<ulong, VoiceState> voiceStates = null)
{
var voiceChannel = GetCachedChannel(model.ChannelId.Value) as CachedVoiceChannel;
var voiceState = new VoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Suppress);
(voiceStates ?? _voiceStates)[model.UserId] = voiceState;
return voiceState;
}
public VoiceState? GetCachedVoiceState(ulong id)
{
VoiceState voiceState;
if (_voiceStates.TryGetValue(id, out voiceState))
return voiceState;
return null;
}
public VoiceState? RemoveCachedVoiceState(ulong id)
{
VoiceState voiceState;
if (_voiceStates.TryRemove(id, out voiceState))
return voiceState;
return null;
}

public override Task<IGuildUser> GetUser(ulong id) => Task.FromResult<IGuildUser>(GetCachedUser(id));
public override Task<IGuildUser> GetCurrentUser()
=> Task.FromResult<IGuildUser>(CurrentUser);
@@ -140,10 +192,11 @@ namespace Discord
=> Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members.OrderBy(x => x.Id).Skip(offset).Take(limit).ToImmutableArray());
public CachedGuildUser AddCachedUser(MemberModel model, ConcurrentDictionary<ulong, CachedGuildUser> members = null, DataStore dataStore = null)
{
var user = Discord.AddCachedUser(model.User);
var user = Discord.GetOrAddCachedUser(model.User);
var member = new CachedGuildUser(this, user, model);
(members ?? _members)[user.Id] = member;
user.AddRef();
DownloadedMemberCount++;
return member;
}
public CachedGuildUser GetCachedUser(ulong id)
@@ -160,7 +213,6 @@ namespace Discord
return member;
return null;
}

public async Task DownloadMembers()
{
if (!HasAllMembers)
@@ -169,7 +221,7 @@ namespace Discord
}
public void CompleteDownloadMembers()
{
_downloaderPromise.SetResult(true);
_downloaderPromise.TrySetResult(true);
}

public CachedGuild Clone() => MemberwiseClone() as CachedGuild;


+ 14
- 3
src/Discord.Net/Entities/WebSocket/CachedGuildUser.cs View File

@@ -2,11 +2,21 @@

namespace Discord
{
internal class CachedGuildUser : GuildUser, ICachedEntity<ulong>
internal class CachedGuildUser : GuildUser, ICachedUser
{
public VoiceChannel VoiceChannel { get; private set; }

public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
public new CachedGuild Guild => base.Guild as CachedGuild;
public new CachedPublicUser User => base.User as CachedPublicUser;

public Presence? Presence => Guild.GetCachedPresence(Id);
public override Game? Game => Presence?.Game;
public override UserStatus Status => Presence?.Status ?? UserStatus.Offline;

public VoiceState? VoiceState => Guild.GetCachedVoiceState(Id);
public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false;
public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false;
public bool IsSuppressed => VoiceState?.IsSuppressed ?? false;
public CachedVoiceChannel VoiceChannel => VoiceState?.VoiceChannel;

public CachedGuildUser(CachedGuild guild, CachedPublicUser user, Model model)
: base(guild, user, model)
@@ -14,5 +24,6 @@ namespace Discord
}

public CachedGuildUser Clone() => MemberwiseClone() as CachedGuildUser;
ICachedUser ICachedUser.Clone() => Clone();
}
}

+ 17
- 1
src/Discord.Net/Entities/WebSocket/CachedPublicUser.cs View File

@@ -1,15 +1,20 @@
using ChannelModel = Discord.API.Channel;
using Model = Discord.API.User;
using PresenceModel = Discord.API.Presence;

namespace Discord
{
internal class CachedPublicUser : User, ICachedEntity<ulong>
internal class CachedPublicUser : User, ICachedUser
{
private int _references;
private Game? _game;
private UserStatus _status;

public CachedDMChannel DMChannel { get; private set; }

public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
public override UserStatus Status => _status;
public override Game? Game => _game;

public CachedPublicUser(DiscordSocketClient discord, Model model)
: base(discord, model)
@@ -39,6 +44,16 @@ namespace Discord
}
}

public void Update(PresenceModel model, UpdateSource source)
{
if (source == UpdateSource.Rest) return;

var game = model.Game != null ? new Game(model.Game) : (Game?)null;

_status = model.Status;
_game = game;
}

public void AddRef()
{
lock (this)
@@ -54,5 +69,6 @@ namespace Discord
}

public CachedPublicUser Clone() => MemberwiseClone() as CachedPublicUser;
ICachedUser ICachedUser.Clone() => Clone();
}
}

+ 2
- 1
src/Discord.Net/Entities/WebSocket/CachedSelfUser.cs View File

@@ -2,7 +2,7 @@

namespace Discord
{
internal class CachedSelfUser : SelfUser, ICachedEntity<ulong>
internal class CachedSelfUser : SelfUser, ICachedUser
{
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;

@@ -12,5 +12,6 @@ namespace Discord
}

public CachedSelfUser Clone() => MemberwiseClone() as CachedSelfUser;
ICachedUser ICachedUser.Clone() => Clone();
}
}

+ 7
- 7
src/Discord.Net/Entities/WebSocket/CachedTextChannel.cs View File

@@ -14,7 +14,7 @@ namespace Discord
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient;
public new CachedGuild Guild => base.Guild as CachedGuild;

public IReadOnlyCollection<IGuildUser> Members
public IReadOnlyCollection<CachedGuildUser> Members
=> Guild.Members.Where(x => Permissions.GetValue(Permissions.ResolveChannel(x, this, x.GuildPermissions.RawValue), ChannelPermission.ReadMessages)).ToImmutableArray();

public CachedTextChannel(CachedGuild guild, Model model)
@@ -23,11 +23,11 @@ namespace Discord
_messages = new MessageCache(Discord, this);
}

public override Task<IGuildUser> GetUser(ulong id) => Task.FromResult(GetCachedUser(id));
public override Task<IReadOnlyCollection<IGuildUser>> GetUsers() => Task.FromResult(Members);
public override Task<IGuildUser> GetUser(ulong id) => Task.FromResult<IGuildUser>(GetCachedUser(id));
public override Task<IReadOnlyCollection<IGuildUser>> GetUsers() => Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members);
public override Task<IReadOnlyCollection<IGuildUser>> GetUsers(int limit, int offset)
=> Task.FromResult<IReadOnlyCollection<IGuildUser>>(Members.Skip(offset).Take(limit).ToImmutableArray());
public IGuildUser GetCachedUser(ulong id)
public CachedGuildUser GetCachedUser(ulong id)
{
var user = Guild.GetCachedUser(id);
if (user != null && Permissions.GetValue(Permissions.ResolveChannel(user, this, user.GuildPermissions.RawValue), ChannelPermission.ReadMessages))
@@ -48,7 +48,7 @@ namespace Discord
return await _messages.Download(fromMessageId, dir, limit).ConfigureAwait(false);
}

public CachedMessage AddCachedMessage(IUser author, MessageModel model)
public CachedMessage AddCachedMessage(ICachedUser author, MessageModel model)
{
var msg = new CachedMessage(this, author, model);
_messages.Add(msg);
@@ -65,10 +65,10 @@ namespace Discord

public CachedTextChannel Clone() => MemberwiseClone() as CachedTextChannel;

IReadOnlyCollection<IUser> ICachedMessageChannel.Members => Members;
IReadOnlyCollection<ICachedUser> ICachedMessageChannel.Members => Members;

IMessage IMessageChannel.GetCachedMessage(ulong id) => GetCachedMessage(id);
IUser ICachedMessageChannel.GetCachedUser(ulong id) => GetCachedUser(id);
ICachedUser ICachedMessageChannel.GetCachedUser(ulong id) => GetCachedUser(id);
ICachedChannel ICachedChannel.Clone() => Clone();
}
}

+ 3
- 3
src/Discord.Net/Entities/WebSocket/ICachedMessageChannel.cs View File

@@ -5,12 +5,12 @@ namespace Discord
{
internal interface ICachedMessageChannel : ICachedChannel, IMessageChannel
{
IReadOnlyCollection<IUser> Members { get; }
IReadOnlyCollection<ICachedUser> Members { get; }

CachedMessage AddCachedMessage(IUser author, MessageModel model);
CachedMessage AddCachedMessage(ICachedUser author, MessageModel model);
new CachedMessage GetCachedMessage(ulong id);
CachedMessage RemoveCachedMessage(ulong id);

IUser GetCachedUser(ulong id);
ICachedUser GetCachedUser(ulong id);
}
}

+ 7
- 0
src/Discord.Net/Entities/WebSocket/ICachedUser.cs View File

@@ -0,0 +1,7 @@
namespace Discord
{
internal interface ICachedUser : IUser, ICachedEntity<ulong>
{
ICachedUser Clone();
}
}

+ 0
- 62
src/Discord.Net/Entities/WebSocket/IVoiceState.cs.old View File

@@ -1,62 +0,0 @@
/*using System;
using Model = Discord.API.MemberVoiceState;

namespace Discord.WebSocket
{
internal class VoiceState : IVoiceState
{
[Flags]
private enum VoiceStates : byte
{
None = 0x0,
Muted = 0x01,
Deafened = 0x02,
Suppressed = 0x4,
SelfMuted = 0x10,
SelfDeafened = 0x20,
}
private VoiceStates _voiceStates;

public Guild Guild { get; }
public ulong UserId { get; }

/// <summary> Gets this user's current voice channel. </summary>
public VoiceChannel VoiceChannel { get; set; }
/// <summary> Returns true if this user has marked themselves as muted. </summary>
public bool IsSelfMuted => (_voiceStates & VoiceStates.SelfMuted) != 0;
/// <summary> Returns true if this user has marked themselves as deafened. </summary>
public bool IsSelfDeafened => (_voiceStates & VoiceStates.SelfDeafened) != 0;
/// <summary> Returns true if the guild is blocking audio from this user. </summary>
public bool IsMuted => (_voiceStates & VoiceStates.Muted) != 0;
/// <summary> Returns true if the guild is blocking audio to this user. </summary>
public bool IsDeafened => (_voiceStates & VoiceStates.Deafened) != 0;
/// <summary> Returns true if the guild is temporarily blocking audio to/from this user. </summary>
public bool IsSuppressed => (_voiceStates & VoiceStates.Suppressed) != 0;

public VoiceState(ulong userId, Guild guild)
{
UserId = userId;
Guild = guild;
}

private void Update(Model model, UpdateSource source)
{
if (model.IsMuted == true)
_voiceStates |= VoiceStates.Muted;
else if (model.IsMuted == false)
_voiceStates &= ~VoiceStates.Muted;

if (model.IsDeafened == true)
_voiceStates |= VoiceStates.Deafened;
else if (model.IsDeafened == false)
_voiceStates &= ~VoiceStates.Deafened;

if (model.IsSuppressed == true)
_voiceStates |= VoiceStates.Suppressed;
else if (model.IsSuppressed == false)
_voiceStates &= ~VoiceStates.Suppressed;
}
}
}*/

+ 42
- 0
src/Discord.Net/Entities/WebSocket/VoiceState.cs View File

@@ -0,0 +1,42 @@
using System;

namespace Discord
{
internal struct VoiceState : IVoiceState
{
[Flags]
private enum Flags : byte
{
None = 0x0,
Suppressed = 0x1,
SelfMuted = 0x2,
SelfDeafened = 0x4,
}

private readonly Flags _voiceStates;
public CachedVoiceChannel VoiceChannel { get; }
public string VoiceSessionId { get; }

public bool IsSelfMuted => (_voiceStates & Flags.SelfMuted) != 0;
public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0;
public bool IsSuppressed => (_voiceStates & Flags.Suppressed) != 0;
public VoiceState(CachedVoiceChannel voiceChannel, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isSuppressed)
{
VoiceChannel = voiceChannel;
VoiceSessionId = sessionId;

Flags voiceStates = Flags.None;
if (isSelfMuted)
voiceStates |= Flags.SelfMuted;
if (isSelfDeafened)
voiceStates |= Flags.SelfDeafened;
if (isSuppressed)
voiceStates |= Flags.Suppressed;
_voiceStates = voiceStates;
}

IVoiceChannel IVoiceState.VoiceChannel => VoiceChannel;
}
}

+ 1
- 0
src/Discord.Net/project.json View File

@@ -15,6 +15,7 @@

"buildOptions": {
"allowUnsafe": true,
"define": [ "BENCHMARK" ],
"warningsAsErrors": false
},



Loading…
Cancel
Save