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