diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj
index 8dec9397e..58aa7a35d 100644
--- a/src/Discord.Net.Net45/Discord.Net.csproj
+++ b/src/Discord.Net.Net45/Discord.Net.csproj
@@ -97,6 +97,9 @@
Message.cs
+
+ PackedPermissions.cs
+
Regions.cs
diff --git a/src/Discord.Net/API/Models/Common.cs b/src/Discord.Net/API/Models/Common.cs
index af3398179..2552c5033 100644
--- a/src/Discord.Net/API/Models/Common.cs
+++ b/src/Discord.Net/API/Models/Common.cs
@@ -51,13 +51,52 @@ namespace Discord.API.Models
[JsonProperty(PropertyName = "verified")]
public bool IsVerified;
}
- internal class PresenceUserInfo : UserReference
+ internal class MemberInfo
+ {
+ [JsonProperty(PropertyName = "user_id")]
+ public string UserId;
+ [JsonProperty(PropertyName = "user")]
+ public UserReference User;
+ [JsonProperty(PropertyName = "guild_id")]
+ public string ServerId;
+ }
+ internal class PresenceMemberInfo : MemberInfo
{
[JsonProperty(PropertyName = "game_id")]
public string GameId;
[JsonProperty(PropertyName = "status")]
public string Status;
}
+ internal class VoiceMemberInfo : MemberInfo
+ {
+ [JsonProperty(PropertyName = "channel_id")]
+ public string ChannelId;
+ [JsonProperty(PropertyName = "suppress")]
+ public bool IsSuppressed;
+ [JsonProperty(PropertyName = "session_id")]
+ public string SessionId;
+ [JsonProperty(PropertyName = "self_mute")]
+ public bool IsSelfMuted;
+ [JsonProperty(PropertyName = "self_deaf")]
+ public bool IsSelfDeafened;
+ [JsonProperty(PropertyName = "mute")]
+ public bool IsMuted;
+ [JsonProperty(PropertyName = "deaf")]
+ public bool IsDeafened;
+ [JsonProperty(PropertyName = "token")]
+ public string Token;
+ }
+ internal class RoleMemberInfo : MemberInfo
+ {
+ [JsonProperty(PropertyName = "mute")]
+ public bool IsMuted;
+ [JsonProperty(PropertyName = "deaf")]
+ public bool IsDeafened;
+ [JsonProperty(PropertyName = "joined_at")]
+ public DateTime? JoinedAt;
+ [JsonProperty(PropertyName = "roles")]
+ public string[] Roles;
+ }
//Channels
internal class ChannelReference
@@ -73,12 +112,26 @@ namespace Discord.API.Models
}
internal class ChannelInfo : ChannelReference
{
+ public sealed class PermissionOverwrite
+ {
+ [JsonProperty(PropertyName = "type")]
+ public string Type;
+ [JsonProperty(PropertyName = "id")]
+ public string Id;
+ [JsonProperty(PropertyName = "deny")]
+ public uint Deny;
+ [JsonProperty(PropertyName = "allow")]
+ public uint Allow;
+ }
+
[JsonProperty(PropertyName = "last_message_id")]
public string LastMessageId;
[JsonProperty(PropertyName = "is_private")]
public bool IsPrivate;
+ [JsonProperty(PropertyName = "position")]
+ public int Position;
[JsonProperty(PropertyName = "permission_overwrites")]
- public object[] PermissionOverwrites;
+ public PermissionOverwrite[] PermissionOverwrites;
[JsonProperty(PropertyName = "recipient")]
public UserReference Recipient;
}
@@ -114,28 +167,14 @@ namespace Discord.API.Models
}
internal class ExtendedServerInfo : ServerInfo
{
- public class Membership
- {
- [JsonProperty(PropertyName = "roles")]
- public string[] Roles;
- [JsonProperty(PropertyName = "mute")]
- public bool IsMuted;
- [JsonProperty(PropertyName = "deaf")]
- public bool IsDeaf;
- [JsonProperty(PropertyName = "joined_at")]
- public DateTime JoinedAt;
- [JsonProperty(PropertyName = "user")]
- public UserReference User;
- }
-
[JsonProperty(PropertyName = "channels")]
public ChannelInfo[] Channels;
[JsonProperty(PropertyName = "members")]
- public Membership[] Members;
- [JsonProperty(PropertyName = "presence")]
- public object[] Presence;
+ public RoleMemberInfo[] Members;
+ [JsonProperty(PropertyName = "presences")]
+ public PresenceMemberInfo[] Presences;
[JsonProperty(PropertyName = "voice_states")]
- public object[] VoiceStates;
+ public VoiceMemberInfo[] VoiceStates;
}
//Messages
@@ -150,7 +189,7 @@ namespace Discord.API.Models
}
internal class Message : MessageReference
{
- public class Attachment
+ public sealed class Attachment
{
[JsonProperty(PropertyName = "id")]
public string Id;
@@ -167,6 +206,40 @@ namespace Discord.API.Models
[JsonProperty(PropertyName = "height")]
public int Height;
}
+ public sealed class Embed
+ {
+ public sealed class ProviderInfo
+ {
+ [JsonProperty(PropertyName = "url")]
+ public string Url;
+ [JsonProperty(PropertyName = "name")]
+ public string Name;
+ }
+ public sealed class ThumbnailInfo
+ {
+ [JsonProperty(PropertyName = "url")]
+ public string Url;
+ [JsonProperty(PropertyName = "proxy_url")]
+ public string ProxyUrl;
+ [JsonProperty(PropertyName = "width")]
+ public int Width;
+ [JsonProperty(PropertyName = "height")]
+ public int Height;
+ }
+
+ [JsonProperty(PropertyName = "url")]
+ public string Url;
+ [JsonProperty(PropertyName = "type")]
+ public string Type;
+ [JsonProperty(PropertyName = "title")]
+ public string Title;
+ [JsonProperty(PropertyName = "description")]
+ public string Description;
+ [JsonProperty(PropertyName = "provider")]
+ public ProviderInfo Provider;
+ [JsonProperty(PropertyName = "thumbnail")]
+ public ThumbnailInfo Thumbnail;
+ }
[JsonProperty(PropertyName = "tts")]
public bool IsTextToSpeech;
@@ -179,7 +252,7 @@ namespace Discord.API.Models
[JsonProperty(PropertyName = "mentions")]
public UserReference[] Mentions;
[JsonProperty(PropertyName = "embeds")]
- public object[] Embeds; //TODO: Parse this
+ public Embed[] Embeds; //TODO: Parse this
[JsonProperty(PropertyName = "attachments")]
public Attachment[] Attachments;
[JsonProperty(PropertyName = "content")]
diff --git a/src/Discord.Net/API/Models/WebSocketCommands.cs b/src/Discord.Net/API/Models/WebSocketCommands.cs
index de675848d..5fc64aa52 100644
--- a/src/Discord.Net/API/Models/WebSocketCommands.cs
+++ b/src/Discord.Net/API/Models/WebSocketCommands.cs
@@ -22,6 +22,8 @@ namespace Discord.API.Models
{
[JsonProperty(PropertyName = "token")]
public string Token;
+ [JsonProperty(PropertyName = "v")]
+ public int Version = 2;
[JsonProperty(PropertyName = "properties")]
public Dictionary Properties = new Dictionary();
}
diff --git a/src/Discord.Net/API/Models/WebSocketEvents.cs b/src/Discord.Net/API/Models/WebSocketEvents.cs
index 7ddab3070..5fa12b017 100644
--- a/src/Discord.Net/API/Models/WebSocketEvents.cs
+++ b/src/Discord.Net/API/Models/WebSocketEvents.cs
@@ -11,12 +11,24 @@ namespace Discord.API.Models
{
public sealed class Ready
{
+ public sealed class ReadStateInfo
+ {
+ [JsonProperty(PropertyName = "id")]
+ public string ChannelId;
+ [JsonProperty(PropertyName = "mention_count")]
+ public int MentionCount;
+ [JsonProperty(PropertyName = "last_message_id")]
+ public string LastMessageId;
+ }
+
+ [JsonProperty(PropertyName = "v")]
+ public int Version;
[JsonProperty(PropertyName = "user")]
public SelfUserInfo User;
[JsonProperty(PropertyName = "session_id")]
public string SessionId;
[JsonProperty(PropertyName = "read_state")]
- public object[] ReadState;
+ public ReadStateInfo[] ReadState;
[JsonProperty(PropertyName = "guilds")]
public ExtendedServerInfo[] Guilds;
[JsonProperty(PropertyName = "private_channels")]
@@ -36,32 +48,15 @@ namespace Discord.API.Models
public sealed class ChannelUpdate : ChannelInfo { }
//Memberships
- public abstract class GuildMemberEvent
- {
- [JsonProperty(PropertyName = "user")]
- public UserReference User;
- [JsonProperty(PropertyName = "guild_id")]
- public string GuildId;
- }
- public sealed class GuildMemberAdd : GuildMemberEvent
- {
- [JsonProperty(PropertyName = "joined_at")]
- public DateTime JoinedAt;
- [JsonProperty(PropertyName = "roles")]
- public string[] Roles;
- }
- public sealed class GuildMemberUpdate : GuildMemberEvent
- {
- [JsonProperty(PropertyName = "roles")]
- public string[] Roles;
- }
- public sealed class GuildMemberRemove : GuildMemberEvent { }
+ public sealed class GuildMemberAdd : RoleMemberInfo { }
+ public sealed class GuildMemberUpdate : RoleMemberInfo { }
+ public sealed class GuildMemberRemove : MemberInfo { }
//Roles
public abstract class GuildRoleEvent
{
[JsonProperty(PropertyName = "guild_id")]
- public string GuildId;
+ public string ServerId;
}
public sealed class GuildRoleCreateUpdate : GuildRoleEvent
{
@@ -78,7 +73,7 @@ namespace Discord.API.Models
public abstract class GuildBanEvent
{
[JsonProperty(PropertyName = "guild_id")]
- public string GuildId;
+ public string ServerId;
}
public sealed class GuildBanAddRemove : GuildBanEvent
{
@@ -93,28 +88,8 @@ namespace Discord.API.Models
//User
public sealed class UserUpdate : SelfUserInfo { }
- public sealed class PresenceUpdate : PresenceUserInfo { }
- public sealed class VoiceStateUpdate
- {
- [JsonProperty(PropertyName = "user_id")]
- public string UserId;
- [JsonProperty(PropertyName = "guild_id")]
- public string GuildId;
- [JsonProperty(PropertyName = "channel_id")]
- public string ChannelId;
- [JsonProperty(PropertyName = "suppress")]
- public bool IsSuppressed;
- [JsonProperty(PropertyName = "session_id")]
- public string SessionId;
- [JsonProperty(PropertyName = "self_mute")]
- public bool IsSelfMuted;
- [JsonProperty(PropertyName = "self_deaf")]
- public bool IsSelfDeafened;
- [JsonProperty(PropertyName = "mute")]
- public bool IsMuted;
- [JsonProperty(PropertyName = "deaf")]
- public bool IsDeafened;
- }
+ public sealed class PresenceUpdate : PresenceMemberInfo { }
+ public sealed class VoiceStateUpdate : VoiceMemberInfo { }
//Chat
public sealed class MessageCreate : Message { }
diff --git a/src/Discord.Net/Channel.cs b/src/Discord.Net/Channel.cs
index 9019b4afb..45a9bbbf1 100644
--- a/src/Discord.Net/Channel.cs
+++ b/src/Discord.Net/Channel.cs
@@ -4,8 +4,16 @@ using System.Linq;
namespace Discord
{
- public sealed class Channel
+ public sealed class Channel
{
+ public sealed class PermissionOverwrite
+ {
+ public string Type { get; internal set; }
+ public string Id { get; internal set; }
+ public PackedPermissions Deny { get; internal set; }
+ public PackedPermissions Allow { get; internal set; }
+ }
+
private readonly DiscordClient _client;
/// Returns the unique identifier for this channel.
@@ -15,8 +23,10 @@ namespace Discord
/// Returns the name of this channel.
public string Name { get { return !IsPrivate ? $"#{_name}" : $"@{Recipient.Name}"; } internal set { _name = value; } }
+ /// Returns the position of this channel in the channel list for this server.
+ public int Position { get; internal set; }
/// Returns false is this is a public chat and true if this is a private chat with another user (see Recipient).
- public bool IsPrivate { get; }
+ public bool IsPrivate { get; }
/// Returns the type of this channel (see ChannelTypes).
public string Type { get; internal set; }
@@ -37,9 +47,8 @@ namespace Discord
[JsonIgnore]
public IEnumerable Messages => _client.Messages.Where(x => x.ChannelId == Id);
- //TODO: Not Implemented
- /// Not implemented, stored for reference.
- public object[] PermissionOverwrites { get; internal set; }
+ /// Returns a collection of all custom permissions used for this channel.
+ public PermissionOverwrite[] PermissionOverwrites { get; internal set; }
internal Channel(string id, string serverId, DiscordClient client)
{
diff --git a/src/Discord.Net/DiscordClient.Events.cs b/src/Discord.Net/DiscordClient.Events.cs
index c3bf1841a..625be27c3 100644
--- a/src/Discord.Net/DiscordClient.Events.cs
+++ b/src/Discord.Net/DiscordClient.Events.cs
@@ -187,19 +187,19 @@ namespace Discord
}
public event EventHandler MemberAdded;
- private void RaiseMemberAdded(Membership membership, Server server)
+ private void RaiseMemberAdded(Membership membership)
{
if (MemberAdded != null)
MemberAdded(this, new MemberEventArgs(membership));
}
public event EventHandler MemberRemoved;
- private void RaiseMemberRemoved(Membership membership, Server server)
+ private void RaiseMemberRemoved(Membership membership)
{
if (MemberRemoved != null)
MemberRemoved(this, new MemberEventArgs(membership));
}
public event EventHandler MemberUpdated;
- private void RaiseMemberUpdated(Membership membership, Server server)
+ private void RaiseMemberUpdated(Membership membership)
{
if (MemberUpdated != null)
MemberUpdated(this, new MemberEventArgs(membership));
@@ -217,11 +217,11 @@ namespace Discord
}
}
- public event EventHandler PresenceUpdated;
- private void RaisePresenceUpdated(User user)
+ public event EventHandler PresenceUpdated;
+ private void RaisePresenceUpdated(Membership member)
{
if (PresenceUpdated != null)
- PresenceUpdated(this, new UserEventArgs(user));
+ PresenceUpdated(this, new MemberEventArgs(member));
}
public event EventHandler VoiceStateUpdated;
private void RaiseVoiceStateUpdated(Membership member)
diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs
index dc7d50205..b0a790a00 100644
--- a/src/Discord.Net/DiscordClient.cs
+++ b/src/Discord.Net/DiscordClient.cs
@@ -1,6 +1,7 @@
using Discord.API;
using Discord.API.Models;
using Discord.Helpers;
+using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
@@ -19,10 +20,13 @@ namespace Discord
private ManualResetEventSlim _isStopping;
private readonly Regex _userRegex, _channelRegex;
private readonly MatchEvaluator _userRegexEvaluator, _channelRegexEvaluator;
+ private readonly JsonSerializer _serializer;
/// Returns the User object for the current logged in user.
public User User { get; private set; }
+ /// Returns the id of the current logged in user.
public string UserId { get; private set; }
+ public string SessionId { get; private set; }
/// Returns a collection of all users the client can see across all servers.
/// This collection does not guarantee any ordering.
@@ -64,6 +68,12 @@ namespace Discord
{
_isStopping = new ManualResetEventSlim(false);
+ _serializer = new JsonSerializer();
+#if TEST_RESPONSES
+ _serializer.CheckAdditionalContent = true;
+ _serializer.MissingMemberHandling = MissingMemberHandling.Error;
+#endif
+
_userRegex = new Regex(@"<@\d+?>", RegexOptions.Compiled);
_channelRegex = new Regex(@"<#\d+?>", RegexOptions.Compiled);
_userRegexEvaluator = new MatchEvaluator(e =>
@@ -90,11 +100,7 @@ namespace Discord
(server, model) =>
{
server.Name = model.Name;
- if (!server.Channels.Any()) //A default channel always exists with the same id as the server.
- {
- var defaultChannel = new ChannelReference() { Id = server.DefaultChannelId, GuildId = server.Id };
- _channels.Update(defaultChannel.Id, defaultChannel.GuildId, defaultChannel);
- }
+ _channels.Update(server.DefaultChannelId, server.Id, null);
if (model is ExtendedServerInfo)
{
var extendedModel = model as ExtendedServerInfo;
@@ -102,9 +108,7 @@ namespace Discord
server.AFKTimeout = extendedModel.AFKTimeout;
server.JoinedAt = extendedModel.JoinedAt ?? DateTime.MinValue;
server.OwnerId = extendedModel.OwnerId;
- server.Presence = extendedModel.Presence;
server.Region = extendedModel.Region;
- server.VoiceStates = extendedModel.VoiceStates;
foreach (var role in extendedModel.Roles)
_roles.Update(role.Id, model.Id, role);
@@ -113,11 +117,13 @@ namespace Discord
foreach (var membership in extendedModel.Members)
{
_users.Update(membership.User.Id, membership.User);
- var newMember = new Membership(server.Id, membership.User.Id, membership.JoinedAt, this);
- newMember.Update(membership);
- server.AddMember(newMember);
+ server.UpdateMember(membership);
}
- }
+ foreach (var membership in extendedModel.VoiceStates)
+ server.UpdateMember(membership);
+ foreach (var membership in extendedModel.Presences)
+ server.UpdateMember(membership);
+ }
},
server => { }
);
@@ -131,13 +137,27 @@ namespace Discord
if (model is ChannelInfo)
{
var extendedModel = model as ChannelInfo;
- channel.PermissionOverwrites = extendedModel.PermissionOverwrites;
+ channel.Position = extendedModel.Position;
+
if (extendedModel.IsPrivate)
{
var user = _users.Update(extendedModel.Recipient.Id, extendedModel.Recipient);
- channel.RecipientId = user.Id;
+ channel.RecipientId = user.Id;
user.PrivateChannelId = channel.Id;
}
+
+ if (extendedModel.PermissionOverwrites != null)
+ {
+ channel.PermissionOverwrites = extendedModel.PermissionOverwrites.Select(x => new Channel.PermissionOverwrite
+ {
+ Type = x.Type,
+ Id = x.Id,
+ Deny = new PackedPermissions(x.Deny),
+ Allow = new PackedPermissions(x.Allow)
+ }).ToArray();
+ }
+ else
+ channel.PermissionOverwrites = null;
}
},
channel =>
@@ -170,8 +190,41 @@ namespace Discord
}).ToArray();
}
else
- extendedModel.Attachments = null;
- message.Embeds = extendedModel.Embeds;
+ message.Attachments = new Message.Attachment[0];
+ if (extendedModel.Embeds != null)
+ {
+ message.Embeds = extendedModel.Embeds.Select(x =>
+ {
+ var embed = new Message.Embed
+ {
+ Url = x.Url,
+ Type = x.Type,
+ Description = x.Description,
+ Title = x.Title
+ };
+ if (x.Provider != null)
+ {
+ embed.Provider = new Message.EmbedProvider
+ {
+ Url = x.Provider.Url,
+ Name = x.Provider.Name
+ };
+ }
+ if (x.Thumbnail != null)
+ {
+ embed.Thumbnail = new Message.File
+ {
+ Url = x.Thumbnail.Url,
+ ProxyUrl = x.Thumbnail.ProxyUrl,
+ Width = x.Thumbnail.Width,
+ Height = x.Thumbnail.Height
+ };
+ }
+ return embed;
+ }).ToArray();
+ }
+ else
+ message.Embeds = new Message.Embed[0];
message.IsMentioningEveryone = extendedModel.IsMentioningEveryone;
message.IsTTS = extendedModel.IsTextToSpeech;
message.MentionIds = extendedModel.Mentions?.Select(x => x.Id)?.ToArray() ?? new string[0];
@@ -206,12 +259,6 @@ namespace Discord
var extendedModel = model as SelfUserInfo;
user.Email = extendedModel.Email;
user.IsVerified = extendedModel.IsVerified;
- }
- if (model is PresenceUserInfo)
- {
- var extendedModel = model as PresenceUserInfo;
- user.GameId = extendedModel.GameId;
- user.Status = extendedModel.Status;
}
},
user => { }
@@ -246,101 +293,104 @@ namespace Discord
//Global
case "READY": //Resync
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
_servers.Clear();
_channels.Clear();
_users.Clear();
UserId = data.User.Id;
+ SessionId = data.SessionId;
User = _users.Update(data.User.Id, data.User);
foreach (var server in data.Guilds)
_servers.Update(server.Id, server);
foreach (var channel in data.PrivateChannels)
_channels.Update(channel.Id, null, channel);
- }
+ }
break;
//Servers
case "GUILD_CREATE":
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
var server = _servers.Update(data.Id, data);
- RaiseServerCreated(server);
+ try { RaiseServerCreated(server); } catch { }
}
break;
case "GUILD_UPDATE":
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
var server = _servers.Update(data.Id, data);
- RaiseServerUpdated(server);
+ try { RaiseServerUpdated(server); } catch { }
}
break;
case "GUILD_DELETE":
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
var server = _servers.Remove(data.Id);
if (server != null)
- RaiseServerDestroyed(server);
+ try { RaiseServerDestroyed(server); } catch { }
}
break;
//Channels
case "CHANNEL_CREATE":
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
var channel = _channels.Update(data.Id, data.GuildId, data);
- RaiseChannelCreated(channel);
+ try { RaiseChannelCreated(channel); } catch { }
}
break;
case "CHANNEL_UPDATE":
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
var channel = _channels.Update(data.Id, data.GuildId, data);
- RaiseChannelUpdated(channel);
+ try { RaiseChannelUpdated(channel); } catch { }
}
break;
case "CHANNEL_DELETE":
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
var channel = _channels.Remove(data.Id);
if (channel != null)
- RaiseChannelDestroyed(channel);
+ try { RaiseChannelDestroyed(channel); } catch { }
}
break;
//Members
case "GUILD_MEMBER_ADD":
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
var user = _users.Update(data.User.Id, data.User);
- var server = _servers[data.GuildId];
- var membership = new Membership(server.Id, data.User.Id, data.JoinedAt, this) { RoleIds = data.Roles };
- server.AddMember(membership);
- RaiseMemberAdded(membership, server);
+ var server = _servers[data.ServerId];
+ if (server != null)
+ {
+ var member = server.UpdateMember(data);
+ try { RaiseMemberAdded(member); } catch { }
+ }
}
break;
case "GUILD_MEMBER_UPDATE":
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
var user = _users.Update(data.User.Id, data.User);
- var server = _servers[data.GuildId];
- var membership = server.GetMember(data.User.Id);
- if (membership != null)
- membership.RoleIds = data.Roles;
- RaiseMemberUpdated(membership, server);
+ var server = _servers[data.ServerId];
+ if (server != null)
+ {
+ var member = server.UpdateMember(data);
+ try { RaiseMemberUpdated(member); } catch { }
+ }
}
break;
case "GUILD_MEMBER_REMOVE":
{
- var data = e.Event.ToObject();
- var user = _users.Update(data.User.Id, data.User);
- var server = _servers[data.GuildId];
+ var data = e.Event.ToObject(_serializer);
+ var server = _servers[data.ServerId];
if (server != null)
{
- var membership = server.RemoveMember(user.Id);
- if (membership != null)
- RaiseMemberRemoved(membership, server);
+ var member = server.RemoveMember(data.UserId);
+ if (member != null)
+ try { RaiseMemberRemoved(member); } catch { }
}
}
break;
@@ -348,121 +398,138 @@ namespace Discord
//Roles
case "GUILD_ROLE_CREATE":
{
- var data = e.Event.ToObject();
- var role = _roles.Update(data.Role.Id, data.GuildId, data.Role);
- RaiseRoleCreated(role);
+ var data = e.Event.ToObject(_serializer);
+ var role = _roles.Update(data.Role.Id, data.ServerId, data.Role);
+ try { RaiseRoleCreated(role); } catch { }
}
break;
case "GUILD_ROLE_UPDATE":
{
- var data = e.Event.ToObject();
- var role = _roles.Update(data.Role.Id, data.GuildId, data.Role);
- RaiseRoleUpdated(role);
+ var data = e.Event.ToObject(_serializer);
+ var role = _roles.Update(data.Role.Id, data.ServerId, data.Role);
+ try { RaiseRoleUpdated(role); } catch { }
}
break;
case "GUILD_ROLE_DELETE":
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
var role = _roles.Remove(data.RoleId);
if (role != null)
- RaiseRoleDeleted(role);
+ try { RaiseRoleDeleted(role); } catch { }
}
break;
//Bans
case "GUILD_BAN_ADD":
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
var user = _users.Update(data.User.Id, data.User);
- var server = _servers[data.GuildId];
- RaiseBanAdded(user, server);
+ var server = _servers[data.ServerId];
+ try { RaiseBanAdded(user, server); } catch { }
}
break;
case "GUILD_BAN_REMOVE":
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
var user = _users.Update(data.User.Id, data.User);
- var server = _servers[data.GuildId];
+ var server = _servers[data.ServerId];
if (server != null && server.RemoveBan(user.Id))
- RaiseBanRemoved(user, server);
+ {
+ try { RaiseBanRemoved(user, server); } catch { }
+ }
}
break;
//Messages
case "MESSAGE_CREATE":
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
var msg = _messages.Update(data.Id, data.ChannelId, data);
msg.User.UpdateActivity(data.Timestamp);
- RaiseMessageCreated(msg);
+ try { RaiseMessageCreated(msg); } catch { }
}
break;
case "MESSAGE_UPDATE":
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
var msg = _messages.Update(data.Id, data.ChannelId, data);
- RaiseMessageUpdated(msg);
+ try { RaiseMessageUpdated(msg); } catch { }
}
break;
case "MESSAGE_DELETE":
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
var msg = GetMessage(data.MessageId);
if (msg != null)
+ {
_messages.Remove(msg.Id);
+ try { RaiseMessageDeleted(msg); } catch { }
+ }
}
break;
case "MESSAGE_ACK":
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
var msg = GetMessage(data.MessageId);
- RaiseMessageAcknowledged(msg);
+ if (msg != null)
+ try { RaiseMessageAcknowledged(msg); } catch { }
}
break;
//Statuses
case "PRESENCE_UPDATE":
{
- var data = e.Event.ToObject();
- var user = _users.Update(data.Id, data);
- RaisePresenceUpdated(user);
+ var data = e.Event.ToObject(_serializer);
+ var user = _users.Update(data.User.Id, data.User);
+ var server = _servers[data.ServerId];
+ if (server != null)
+ {
+ var member = server.UpdateMember(data);
+ try { RaisePresenceUpdated(member); } catch { }
+ }
}
break;
case "VOICE_STATE_UPDATE":
{
- var data = e.Event.ToObject();
- var member = GetMember(data.GuildId, data.UserId);
- if (member != null)
+ var data = e.Event.ToObject(_serializer);
+ var server = _servers[data.ServerId];
+ if (server != null)
{
- member.Update(data);
- RaiseVoiceStateUpdated(member);
+ var member = server.UpdateMember(data);
+ if (member != null)
+ try { RaiseVoiceStateUpdated(member); } catch { }
}
}
break;
case "TYPING_START":
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
var channel = _channels[data.ChannelId];
var user = _users[data.UserId];
- RaiseUserTyping(user, channel);
+ if (user != null)
+ {
+ user.UpdateActivity(DateTime.UtcNow);
+ if (channel != null)
+ try { RaiseUserTyping(user, channel); } catch { }
+ }
}
break;
//Voice
case "VOICE_SERVER_UPDATE":
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
var server = _servers[data.ServerId];
- RaiseVoiceServerUpdated(server, data.Endpoint);
+ try { RaiseVoiceServerUpdated(server, data.Endpoint); } catch { }
}
break;
//Settings
case "USER_UPDATE":
{
- var data = e.Event.ToObject();
+ var data = e.Event.ToObject(_serializer);
var user = _users.Update(data.Id, data);
- RaiseUserUpdated(user);
+ try { RaiseUserUpdated(user); } catch { }
}
break;
case "USER_SETTINGS_UPDATE":
diff --git a/src/Discord.Net/DiscordWebSocket.cs b/src/Discord.Net/DiscordWebSocket.cs
index 01c6144ef..22a26ee59 100644
--- a/src/Discord.Net/DiscordWebSocket.cs
+++ b/src/Discord.Net/DiscordWebSocket.cs
@@ -98,7 +98,8 @@ namespace Discord
{
throw new InvalidOperationException("Bad Token");
}
- _connectWaitOnLogin2.Wait(cancelToken); //Waiting on READY handler
+ try { _connectWaitOnLogin2.Wait(cancelToken); } //Waiting on READY handler
+ catch (OperationCanceledException) { return; }
_isConnected = true;
RaiseConnected();
@@ -150,11 +151,7 @@ namespace Discord
QueueMessage(new WebSocketCommands.KeepAlive());
_connectWaitOnLogin.Set(); //Pre-Event
}
- try
- {
- RaiseGotEvent(msg.Type, msg.Payload as JToken);
- }
- catch { } //Don't allow user exceptions to affect our state
+ RaiseGotEvent(msg.Type, msg.Payload as JToken);
if (msg.Type == "READY")
_connectWaitOnLogin2.Set(); //Post-Event
break;
diff --git a/src/Discord.Net/Helpers/AsyncCache.cs b/src/Discord.Net/Helpers/AsyncCache.cs
index 2fefbb985..570c20879 100644
--- a/src/Discord.Net/Helpers/AsyncCache.cs
+++ b/src/Discord.Net/Helpers/AsyncCache.cs
@@ -14,7 +14,7 @@ namespace Discord.Helpers
private readonly Action _onUpdate;
private readonly Action _onRemove;
- public AsyncCache(Func onCreate, Action onUpdate, Action onRemove)
+ public AsyncCache(Func onCreate, Action onUpdate, Action onRemove = null)
{
_dictionary = new ConcurrentDictionary();
_onCreate = onCreate;
@@ -49,7 +49,8 @@ namespace Discord.Helpers
isNew = !_dictionary.TryGetValue(key, out value);
if (isNew)
value = _onCreate(key, parentKey);
- _onUpdate(value, model);
+ if (model != null)
+ _onUpdate(value, model);
if (isNew)
{
//If this fails, repeat as an update instead of an add
@@ -68,7 +69,11 @@ namespace Discord.Helpers
{
TValue value = null;
if (_dictionary.TryRemove(key, out value))
+ {
+ if (_onRemove != null)
+ _onRemove(value);
return value;
+ }
else
return null;
}
diff --git a/src/Discord.Net/Helpers/Http.cs b/src/Discord.Net/Helpers/Http.cs
index 55c99edef..943a486d6 100644
--- a/src/Discord.Net/Helpers/Http.cs
+++ b/src/Discord.Net/Helpers/Http.cs
@@ -10,7 +10,6 @@ using System.Globalization;
#if TEST_RESPONSES
using System.Diagnostics;
-using JsonErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
#endif
namespace Discord.Helpers
diff --git a/src/Discord.Net/Membership.cs b/src/Discord.Net/Membership.cs
index 64f95f9ed..c0c5d6045 100644
--- a/src/Discord.Net/Membership.cs
+++ b/src/Discord.Net/Membership.cs
@@ -18,6 +18,11 @@ namespace Discord
public bool IsSuppressed { get; internal set; }
public string SessionId { get; internal set; }
+ public string Token { get; internal set; }
+ /// Returns the id for the game this user is currently playing.
+ public string GameId { get; internal set; }
+ /// Returns the current status for this user.
+ public string Status { get; internal set; }
public string ServerId { get; }
public Server Server => _client.GetServer(ServerId);
@@ -31,29 +36,11 @@ namespace Discord
public string[] RoleIds { get; internal set; }
public IEnumerable Roles => RoleIds.Select(x => _client.GetRole(x));
- public Membership(string serverId, string userId, DateTime joinedAt, DiscordClient client)
+ public Membership(string serverId, string userId, DiscordClient client)
{
ServerId = serverId;
UserId = userId;
- JoinedAt = joinedAt;
_client = client;
}
-
- internal void Update(ExtendedServerInfo.Membership data)
- {
- IsDeafened = data.IsDeaf;
- IsMuted = data.IsMuted;
- RoleIds = data.Roles;
- }
- internal void Update(WebSocketEvents.VoiceStateUpdate data)
- {
- VoiceChannelId = data.ChannelId;
- IsDeafened = data.IsDeafened;
- IsMuted = data.IsMuted;
- IsSelfDeafened = data.IsSelfDeafened;
- IsSelfMuted = data.IsSelfMuted;
- IsSuppressed = data.IsSuppressed;
- SessionId = data.SessionId;
- }
}
}
diff --git a/src/Discord.Net/Message.cs b/src/Discord.Net/Message.cs
index 827cdb36e..eef21ee28 100644
--- a/src/Discord.Net/Message.cs
+++ b/src/Discord.Net/Message.cs
@@ -7,10 +7,39 @@ namespace Discord
{
public sealed class Message
{
- public class Attachment
+ public sealed class Attachment : File
{
/// Unique identifier for this file.
public string Id { get; internal set; }
+ /// Size, in bytes, of this file file.
+ public int Size { get; internal set; }
+ /// Filename of this file.
+ public string Filename { get; internal set; }
+ }
+ public sealed class Embed
+ {
+ /// URL of this embed.
+ public string Url { get; internal set; }
+ /// Type of this embed.
+ public string Type { get; internal set; }
+ /// Title for this embed.
+ public string Title { get; internal set; }
+ /// Summary of this embed.
+ public string Description { get; internal set; }
+ /// Returns information about the providing website of this embed.
+ public EmbedProvider Provider { get; internal set; }
+ /// Returns the thumbnail of this embed.
+ public File Thumbnail { get; internal set; }
+ }
+ public sealed class EmbedProvider
+ {
+ /// URL of this embed provider.
+ public string Url { get; internal set; }
+ /// Name of this embed provider.
+ public string Name { get; internal set; }
+ }
+ public class File
+ {
/// Download url for this file.
public string Url { get; internal set; }
/// Preview url for this file.
@@ -19,10 +48,6 @@ namespace Discord
public int? Width { get; internal set; }
/// Height of this file, if it is an image.
public int? Height { get; internal set; }
- /// Size, in bytes, of this file file.
- public int Size { get; internal set; }
- /// Filename of this file.
- public string Filename { get; internal set; }
}
private readonly DiscordClient _client;
@@ -49,6 +74,9 @@ namespace Discord
public DateTime? EditedTimestamp { get; internal set; }
/// Returns the attachments included in this message.
public Attachment[] Attachments { get; internal set; }
+ //TODO: Not Implemented
+ /// Returns a collection of all embeded content in this message.
+ public Embed[] Embeds { get; internal set; }
/// Returns a collection of all user ids mentioned in this message.
public string[] MentionIds { get; internal set; }
@@ -68,10 +96,6 @@ namespace Discord
[JsonIgnore]
public User User => _client.GetUser(UserId);
- //TODO: Not Implemented
- /// Not implemented, stored for reference.
- public object[] Embeds { get; internal set; }
-
internal Message(string id, string channelId, DiscordClient client)
{
Id = id;
diff --git a/src/Discord.Net/PackedPermissions.cs b/src/Discord.Net/PackedPermissions.cs
new file mode 100644
index 000000000..3b1bd4b50
--- /dev/null
+++ b/src/Discord.Net/PackedPermissions.cs
@@ -0,0 +1,67 @@
+namespace Discord
+{
+ public sealed class PackedPermissions
+ {
+ private uint _rawValue;
+ internal uint RawValue { get { return _rawValue; } set { _rawValue = value; } }
+
+ internal PackedPermissions() { }
+ internal PackedPermissions(uint rawValue) { _rawValue = rawValue; }
+
+ /// If True, a user may create invites.
+ public bool General_CreateInstantInvite => ((_rawValue >> 0) & 0x1) == 1;
+ /// If True, a user may ban users from the server.
+ public bool General_BanMembers => ((_rawValue >> 1) & 0x1) == 1;
+ /// If True, a user may kick users from the server.
+ public bool General_KickMembers => ((_rawValue >> 2) & 0x1) == 1;
+ /// If True, a user adjust roles.
+ /// Having this permission effectively gives all the others as a user may add them to themselves.
+ public bool General_ManageRoles => ((_rawValue >> 3) & 0x1) == 1;
+ /// If True, a user may create, delete and modify channels.
+ public bool General_ManageChannels => ((_rawValue >> 4) & 0x1) == 1;
+ /// If True, a user may adjust server properties.
+ public bool General_ManageServer => ((_rawValue >> 5) & 0x1) == 1;
+
+ //4 Unused
+
+ /// If True, a user may join channels.
+ /// Note that without this permission, a channel is not sent by the server.
+ public bool Text_ReadMessages => ((_rawValue >> 10) & 0x1) == 1;
+ /// If True, a user may send messages.
+ public bool Text_SendMessages => ((_rawValue >> 11) & 0x1) == 1;
+ /// If True, a user may send text-to-speech messages.
+ public bool Text_SendTTSMessages => ((_rawValue >> 12) & 0x1) == 1;
+ /// If True, a user may delete messages.
+ public bool Text_ManageMessages => ((_rawValue >> 13) & 0x1) == 1;
+ /// If True, Discord will auto-embed links sent by this user.
+ public bool Text_EmbedLinks => ((_rawValue >> 14) & 0x1) == 1;
+ /// If True, a user may send files.
+ public bool Text_AttachFiles => ((_rawValue >> 15) & 0x1) == 1;
+ /// If True, a user may read previous messages.
+ public bool Text_ReadMessageHistory => ((_rawValue >> 16) & 0x1) == 1;
+ /// If True, a user may mention @everyone.
+ public bool Text_MentionEveryone => ((_rawValue >> 17) & 0x1) == 1;
+
+ //2 Unused
+
+ /// If True, a user may connect to a voice channel.
+ public bool Voice_Connect => ((_rawValue >> 20) & 0x1) == 1;
+ /// If True, a user may speak in a voice channel.
+ public bool Voice_Speak => ((_rawValue >> 21) & 0x1) == 1;
+ /// If True, a user may mute users.
+ public bool Voice_MuteMembers => ((_rawValue >> 22) & 0x1) == 1;
+ /// If True, a user may deafen users.
+ public bool Voice_DeafenMembers => ((_rawValue >> 23) & 0x1) == 1;
+ /// If True, a user may move other users between voice channels.
+ public bool Voice_MoveMembers => ((_rawValue >> 24) & 0x1) == 1;
+ /// If True, a user may use voice activation rather than push-to-talk.
+ public bool Voice_UseVoiceActivation => ((_rawValue >> 25) & 0x1) == 1;
+
+ //6 Unused
+
+ public static implicit operator uint (PackedPermissions perms)
+ {
+ return perms._rawValue;
+ }
+ }
+}
diff --git a/src/Discord.Net/Role.cs b/src/Discord.Net/Role.cs
index cdf61c5be..515274710 100644
--- a/src/Discord.Net/Role.cs
+++ b/src/Discord.Net/Role.cs
@@ -4,70 +4,6 @@ namespace Discord
{
public sealed class Role
{
- public sealed class PackedPermissions
- {
- private uint _rawValue;
- internal uint RawValue { get { return _rawValue; } set { _rawValue = value; } }
-
- internal PackedPermissions() { }
-
- /// If True, a user may create invites.
- public bool General_CreateInstantInvite => ((_rawValue >> 0) & 0x1) == 1;
- /// If True, a user may ban users from the server.
- public bool General_BanMembers => ((_rawValue >> 1) & 0x1) == 1;
- /// If True, a user may kick users from the server.
- public bool General_KickMembers => ((_rawValue >> 2) & 0x1) == 1;
- /// If True, a user adjust roles.
- /// Having this permission effectively gives all the others as a user may add them to themselves.
- public bool General_ManageRoles => ((_rawValue >> 3) & 0x1) == 1;
- /// If True, a user may create, delete and modify channels.
- public bool General_ManageChannels => ((_rawValue >> 4) & 0x1) == 1;
- /// If True, a user may adjust server properties.
- public bool General_ManageServer => ((_rawValue >> 5) & 0x1) == 1;
-
- //4 Unused
-
- /// If True, a user may join channels.
- /// Note that without this permission, a channel is not sent by the server.
- public bool Text_ReadMessages => ((_rawValue >> 10) & 0x1) == 1;
- /// If True, a user may send messages.
- public bool Text_SendMessages => ((_rawValue >> 11) & 0x1) == 1;
- /// If True, a user may send text-to-speech messages.
- public bool Text_SendTTSMessages => ((_rawValue >> 12) & 0x1) == 1;
- /// If True, a user may delete messages.
- public bool Text_ManageMessages => ((_rawValue >> 13) & 0x1) == 1;
- /// If True, Discord will auto-embed links sent by this user.
- public bool Text_EmbedLinks => ((_rawValue >> 14) & 0x1) == 1;
- /// If True, a user may send files.
- public bool Text_AttachFiles => ((_rawValue >> 15) & 0x1) == 1;
- /// If True, a user may read previous messages.
- public bool Text_ReadMessageHistory => ((_rawValue >> 16) & 0x1) == 1;
- /// If True, a user may mention @everyone.
- public bool Text_MentionEveryone => ((_rawValue >> 17) & 0x1) == 1;
-
- //2 Unused
-
- /// If True, a user may connect to a voice channel.
- public bool Voice_Connect => ((_rawValue >> 20) & 0x1) == 1;
- /// If True, a user may speak in a voice channel.
- public bool Voice_Speak => ((_rawValue >> 21) & 0x1) == 1;
- /// If True, a user may mute users.
- public bool Voice_MuteMembers => ((_rawValue >> 22) & 0x1) == 1;
- /// If True, a user may deafen users.
- public bool Voice_DeafenMembers => ((_rawValue >> 23) & 0x1) == 1;
- /// If True, a user may move other users between voice channels.
- public bool Voice_MoveMembers => ((_rawValue >> 24) & 0x1) == 1;
- /// If True, a user may use voice activation rather than push-to-talk.
- public bool Voice_UseVoiceActivation => ((_rawValue >> 25) & 0x1) == 1;
-
- //6 Unused
-
- public static implicit operator uint(PackedPermissions perms)
- {
- return perms._rawValue;
- }
- }
-
private readonly DiscordClient _client;
/// Returns the unique identifier for this role.
diff --git a/src/Discord.Net/Server.cs b/src/Discord.Net/Server.cs
index 7031bf9e4..9e2bf851e 100644
--- a/src/Discord.Net/Server.cs
+++ b/src/Discord.Net/Server.cs
@@ -1,4 +1,5 @@
-using System;
+using Discord.Helpers;
+using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
@@ -38,9 +39,9 @@ namespace Discord
/// Returns the default channel for this server.
public Channel DefaultChannel =>_client.GetChannel(DefaultChannelId);
- internal ConcurrentDictionary _members;
+ internal AsyncCache _members;
/// Returns a collection of all channels within this server.
- public IEnumerable Members => _members.Values;
+ public IEnumerable Members => _members;
internal ConcurrentDictionary _bans;
/// Returns a collection of all users banned on this server.
@@ -54,38 +55,59 @@ namespace Discord
/// Returns a collection of all roles within this server.
public IEnumerable Roles => _client.Roles.Where(x => x.ServerId == Id);
- //TODO: Not Implemented
- /// Not implemented, stored for reference.
- public object Presence { get; internal set; }
- //TODO: Not Implemented
- /// Not implemented, stored for reference.
- public object[] VoiceStates { get; internal set; }
-
internal Server(string id, DiscordClient client)
{
Id = id;
_client = client;
- _members = new ConcurrentDictionary();
_bans = new ConcurrentDictionary();
+ _members = new AsyncCache(
+ (key, parentKey) => new Membership(parentKey, key, _client),
+ (member, model) =>
+ {
+ if (model is API.Models.PresenceMemberInfo)
+ {
+ var extendedModel = model as API.Models.PresenceMemberInfo;
+ member.Status = extendedModel.Status;
+ member.GameId = extendedModel.GameId;
+ }
+ if (model is API.Models.VoiceMemberInfo)
+ {
+ var extendedModel = model as API.Models.VoiceMemberInfo;
+ member.VoiceChannelId = extendedModel.ChannelId;
+ member.IsDeafened = extendedModel.IsDeafened;
+ member.IsMuted = extendedModel.IsMuted;
+ member.IsSelfDeafened = extendedModel.IsSelfDeafened;
+ member.IsSelfMuted = extendedModel.IsSelfMuted;
+ member.IsSuppressed = extendedModel.IsSuppressed;
+ member.SessionId = extendedModel.SessionId;
+ member.Token = extendedModel.Token;
+ }
+ if (model is API.Models.RoleMemberInfo)
+ {
+ var extendedModel = model as API.Models.RoleMemberInfo;
+ member.IsDeafened = extendedModel.IsDeafened;
+ member.IsMuted = extendedModel.IsMuted;
+ member.RoleIds = extendedModel.Roles;
+ if (extendedModel.JoinedAt.HasValue)
+ member.JoinedAt = extendedModel.JoinedAt.Value;
+ }
+ }
+ );
}
- internal void AddMember(Membership membership)
+ internal Membership UpdateMember(API.Models.MemberInfo membership)
{
- _members[membership.UserId] = membership;
+ return _members.Update(membership.User?.Id ?? membership.UserId, Id, membership);
}
internal Membership RemoveMember(string userId)
{
- Membership result = null;
- _members.TryRemove(userId, out result);
- return result;
+ return _members.Remove(userId);
}
public Membership GetMembership(User user)
=> GetMember(user.Id);
public Membership GetMember(string userId)
{
- Membership result = null;
- _members.TryGetValue(userId, out result);
- return result;
+ return _members[userId];
}
internal void AddBan(string banId)
diff --git a/src/Discord.Net/User.cs b/src/Discord.Net/User.cs
index eee366cab..2475c1d28 100644
--- a/src/Discord.Net/User.cs
+++ b/src/Discord.Net/User.cs
@@ -27,10 +27,6 @@ namespace Discord
/// Returns if the email for this user has been verified.
/// This field is only ever populated for the current logged in user.
public bool IsVerified { get; internal set; }
- /// Returns the id for the game this user is currently playing.
- public string GameId { get; internal set; }
- /// Returns the current status for this user.
- public string Status { get; internal set; }
/// Returns the string "<@Id>" to be used as a shortcut when including mentions in text.
public string Mention { get { return $"<@{Id}>"; } }