| @@ -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}>"; } } | ||||