| @@ -97,6 +97,9 @@ | |||
| <Compile Include="..\Discord.Net\Message.cs"> | |||
| <Link>Message.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\PackedPermissions.cs"> | |||
| <Link>PackedPermissions.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\Discord.Net\Regions.cs"> | |||
| <Link>Regions.cs</Link> | |||
| </Compile> | |||
| @@ -51,13 +51,52 @@ namespace Discord.API.Models | |||
| [JsonProperty(PropertyName = "verified")] | |||
| public bool IsVerified; | |||
| } | |||
| internal class PresenceUserInfo : UserReference | |||
| internal class MemberInfo | |||
| { | |||
| [JsonProperty(PropertyName = "user_id")] | |||
| public string UserId; | |||
| [JsonProperty(PropertyName = "user")] | |||
| public UserReference User; | |||
| [JsonProperty(PropertyName = "guild_id")] | |||
| public string ServerId; | |||
| } | |||
| internal class PresenceMemberInfo : MemberInfo | |||
| { | |||
| [JsonProperty(PropertyName = "game_id")] | |||
| public string GameId; | |||
| [JsonProperty(PropertyName = "status")] | |||
| public string Status; | |||
| } | |||
| internal class VoiceMemberInfo : MemberInfo | |||
| { | |||
| [JsonProperty(PropertyName = "channel_id")] | |||
| public string ChannelId; | |||
| [JsonProperty(PropertyName = "suppress")] | |||
| public bool IsSuppressed; | |||
| [JsonProperty(PropertyName = "session_id")] | |||
| public string SessionId; | |||
| [JsonProperty(PropertyName = "self_mute")] | |||
| public bool IsSelfMuted; | |||
| [JsonProperty(PropertyName = "self_deaf")] | |||
| public bool IsSelfDeafened; | |||
| [JsonProperty(PropertyName = "mute")] | |||
| public bool IsMuted; | |||
| [JsonProperty(PropertyName = "deaf")] | |||
| public bool IsDeafened; | |||
| [JsonProperty(PropertyName = "token")] | |||
| public string Token; | |||
| } | |||
| internal class RoleMemberInfo : MemberInfo | |||
| { | |||
| [JsonProperty(PropertyName = "mute")] | |||
| public bool IsMuted; | |||
| [JsonProperty(PropertyName = "deaf")] | |||
| public bool IsDeafened; | |||
| [JsonProperty(PropertyName = "joined_at")] | |||
| public DateTime? JoinedAt; | |||
| [JsonProperty(PropertyName = "roles")] | |||
| public string[] Roles; | |||
| } | |||
| //Channels | |||
| internal class ChannelReference | |||
| @@ -73,12 +112,26 @@ namespace Discord.API.Models | |||
| } | |||
| internal class ChannelInfo : ChannelReference | |||
| { | |||
| public sealed class PermissionOverwrite | |||
| { | |||
| [JsonProperty(PropertyName = "type")] | |||
| public string Type; | |||
| [JsonProperty(PropertyName = "id")] | |||
| public string Id; | |||
| [JsonProperty(PropertyName = "deny")] | |||
| public uint Deny; | |||
| [JsonProperty(PropertyName = "allow")] | |||
| public uint Allow; | |||
| } | |||
| [JsonProperty(PropertyName = "last_message_id")] | |||
| public string LastMessageId; | |||
| [JsonProperty(PropertyName = "is_private")] | |||
| public bool IsPrivate; | |||
| [JsonProperty(PropertyName = "position")] | |||
| public int Position; | |||
| [JsonProperty(PropertyName = "permission_overwrites")] | |||
| public object[] PermissionOverwrites; | |||
| public PermissionOverwrite[] PermissionOverwrites; | |||
| [JsonProperty(PropertyName = "recipient")] | |||
| public UserReference Recipient; | |||
| } | |||
| @@ -114,28 +167,14 @@ namespace Discord.API.Models | |||
| } | |||
| internal class ExtendedServerInfo : ServerInfo | |||
| { | |||
| public class Membership | |||
| { | |||
| [JsonProperty(PropertyName = "roles")] | |||
| public string[] Roles; | |||
| [JsonProperty(PropertyName = "mute")] | |||
| public bool IsMuted; | |||
| [JsonProperty(PropertyName = "deaf")] | |||
| public bool IsDeaf; | |||
| [JsonProperty(PropertyName = "joined_at")] | |||
| public DateTime JoinedAt; | |||
| [JsonProperty(PropertyName = "user")] | |||
| public UserReference User; | |||
| } | |||
| [JsonProperty(PropertyName = "channels")] | |||
| public ChannelInfo[] Channels; | |||
| [JsonProperty(PropertyName = "members")] | |||
| public Membership[] Members; | |||
| [JsonProperty(PropertyName = "presence")] | |||
| public object[] Presence; | |||
| public RoleMemberInfo[] Members; | |||
| [JsonProperty(PropertyName = "presences")] | |||
| public PresenceMemberInfo[] Presences; | |||
| [JsonProperty(PropertyName = "voice_states")] | |||
| public object[] VoiceStates; | |||
| public VoiceMemberInfo[] VoiceStates; | |||
| } | |||
| //Messages | |||
| @@ -150,7 +189,7 @@ namespace Discord.API.Models | |||
| } | |||
| internal class Message : MessageReference | |||
| { | |||
| public class Attachment | |||
| public sealed class Attachment | |||
| { | |||
| [JsonProperty(PropertyName = "id")] | |||
| public string Id; | |||
| @@ -167,6 +206,40 @@ namespace Discord.API.Models | |||
| [JsonProperty(PropertyName = "height")] | |||
| public int Height; | |||
| } | |||
| public sealed class Embed | |||
| { | |||
| public sealed class ProviderInfo | |||
| { | |||
| [JsonProperty(PropertyName = "url")] | |||
| public string Url; | |||
| [JsonProperty(PropertyName = "name")] | |||
| public string Name; | |||
| } | |||
| public sealed class ThumbnailInfo | |||
| { | |||
| [JsonProperty(PropertyName = "url")] | |||
| public string Url; | |||
| [JsonProperty(PropertyName = "proxy_url")] | |||
| public string ProxyUrl; | |||
| [JsonProperty(PropertyName = "width")] | |||
| public int Width; | |||
| [JsonProperty(PropertyName = "height")] | |||
| public int Height; | |||
| } | |||
| [JsonProperty(PropertyName = "url")] | |||
| public string Url; | |||
| [JsonProperty(PropertyName = "type")] | |||
| public string Type; | |||
| [JsonProperty(PropertyName = "title")] | |||
| public string Title; | |||
| [JsonProperty(PropertyName = "description")] | |||
| public string Description; | |||
| [JsonProperty(PropertyName = "provider")] | |||
| public ProviderInfo Provider; | |||
| [JsonProperty(PropertyName = "thumbnail")] | |||
| public ThumbnailInfo Thumbnail; | |||
| } | |||
| [JsonProperty(PropertyName = "tts")] | |||
| public bool IsTextToSpeech; | |||
| @@ -179,7 +252,7 @@ namespace Discord.API.Models | |||
| [JsonProperty(PropertyName = "mentions")] | |||
| public UserReference[] Mentions; | |||
| [JsonProperty(PropertyName = "embeds")] | |||
| public object[] Embeds; //TODO: Parse this | |||
| public Embed[] Embeds; //TODO: Parse this | |||
| [JsonProperty(PropertyName = "attachments")] | |||
| public Attachment[] Attachments; | |||
| [JsonProperty(PropertyName = "content")] | |||
| @@ -22,6 +22,8 @@ namespace Discord.API.Models | |||
| { | |||
| [JsonProperty(PropertyName = "token")] | |||
| public string Token; | |||
| [JsonProperty(PropertyName = "v")] | |||
| public int Version = 2; | |||
| [JsonProperty(PropertyName = "properties")] | |||
| public Dictionary<string, string> Properties = new Dictionary<string, string>(); | |||
| } | |||
| @@ -11,12 +11,24 @@ namespace Discord.API.Models | |||
| { | |||
| public sealed class Ready | |||
| { | |||
| public sealed class ReadStateInfo | |||
| { | |||
| [JsonProperty(PropertyName = "id")] | |||
| public string ChannelId; | |||
| [JsonProperty(PropertyName = "mention_count")] | |||
| public int MentionCount; | |||
| [JsonProperty(PropertyName = "last_message_id")] | |||
| public string LastMessageId; | |||
| } | |||
| [JsonProperty(PropertyName = "v")] | |||
| public int Version; | |||
| [JsonProperty(PropertyName = "user")] | |||
| public SelfUserInfo User; | |||
| [JsonProperty(PropertyName = "session_id")] | |||
| public string SessionId; | |||
| [JsonProperty(PropertyName = "read_state")] | |||
| public object[] ReadState; | |||
| public ReadStateInfo[] ReadState; | |||
| [JsonProperty(PropertyName = "guilds")] | |||
| public ExtendedServerInfo[] Guilds; | |||
| [JsonProperty(PropertyName = "private_channels")] | |||
| @@ -36,32 +48,15 @@ namespace Discord.API.Models | |||
| public sealed class ChannelUpdate : ChannelInfo { } | |||
| //Memberships | |||
| public abstract class GuildMemberEvent | |||
| { | |||
| [JsonProperty(PropertyName = "user")] | |||
| public UserReference User; | |||
| [JsonProperty(PropertyName = "guild_id")] | |||
| public string GuildId; | |||
| } | |||
| public sealed class GuildMemberAdd : GuildMemberEvent | |||
| { | |||
| [JsonProperty(PropertyName = "joined_at")] | |||
| public DateTime JoinedAt; | |||
| [JsonProperty(PropertyName = "roles")] | |||
| public string[] Roles; | |||
| } | |||
| public sealed class GuildMemberUpdate : GuildMemberEvent | |||
| { | |||
| [JsonProperty(PropertyName = "roles")] | |||
| public string[] Roles; | |||
| } | |||
| public sealed class GuildMemberRemove : GuildMemberEvent { } | |||
| public sealed class GuildMemberAdd : RoleMemberInfo { } | |||
| public sealed class GuildMemberUpdate : RoleMemberInfo { } | |||
| public sealed class GuildMemberRemove : MemberInfo { } | |||
| //Roles | |||
| public abstract class GuildRoleEvent | |||
| { | |||
| [JsonProperty(PropertyName = "guild_id")] | |||
| public string GuildId; | |||
| public string ServerId; | |||
| } | |||
| public sealed class GuildRoleCreateUpdate : GuildRoleEvent | |||
| { | |||
| @@ -78,7 +73,7 @@ namespace Discord.API.Models | |||
| public abstract class GuildBanEvent | |||
| { | |||
| [JsonProperty(PropertyName = "guild_id")] | |||
| public string GuildId; | |||
| public string ServerId; | |||
| } | |||
| public sealed class GuildBanAddRemove : GuildBanEvent | |||
| { | |||
| @@ -93,28 +88,8 @@ namespace Discord.API.Models | |||
| //User | |||
| public sealed class UserUpdate : SelfUserInfo { } | |||
| public sealed class PresenceUpdate : PresenceUserInfo { } | |||
| public sealed class VoiceStateUpdate | |||
| { | |||
| [JsonProperty(PropertyName = "user_id")] | |||
| public string UserId; | |||
| [JsonProperty(PropertyName = "guild_id")] | |||
| public string GuildId; | |||
| [JsonProperty(PropertyName = "channel_id")] | |||
| public string ChannelId; | |||
| [JsonProperty(PropertyName = "suppress")] | |||
| public bool IsSuppressed; | |||
| [JsonProperty(PropertyName = "session_id")] | |||
| public string SessionId; | |||
| [JsonProperty(PropertyName = "self_mute")] | |||
| public bool IsSelfMuted; | |||
| [JsonProperty(PropertyName = "self_deaf")] | |||
| public bool IsSelfDeafened; | |||
| [JsonProperty(PropertyName = "mute")] | |||
| public bool IsMuted; | |||
| [JsonProperty(PropertyName = "deaf")] | |||
| public bool IsDeafened; | |||
| } | |||
| public sealed class PresenceUpdate : PresenceMemberInfo { } | |||
| public sealed class VoiceStateUpdate : VoiceMemberInfo { } | |||
| //Chat | |||
| public sealed class MessageCreate : Message { } | |||
| @@ -4,8 +4,16 @@ using System.Linq; | |||
| namespace Discord | |||
| { | |||
| public sealed class Channel | |||
| public sealed class Channel | |||
| { | |||
| public sealed class PermissionOverwrite | |||
| { | |||
| public string Type { get; internal set; } | |||
| public string Id { get; internal set; } | |||
| public PackedPermissions Deny { get; internal set; } | |||
| public PackedPermissions Allow { get; internal set; } | |||
| } | |||
| private readonly DiscordClient _client; | |||
| /// <summary> Returns the unique identifier for this channel. </summary> | |||
| @@ -15,8 +23,10 @@ namespace Discord | |||
| /// <summary> Returns the name of this channel. </summary> | |||
| 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> | |||
| public bool IsPrivate { get; } | |||
| public bool IsPrivate { get; } | |||
| /// <summary> Returns the type of this channel (see ChannelTypes). </summary> | |||
| public string Type { get; internal set; } | |||
| @@ -37,9 +47,8 @@ namespace Discord | |||
| [JsonIgnore] | |||
| 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) | |||
| { | |||
| @@ -187,19 +187,19 @@ namespace Discord | |||
| } | |||
| public event EventHandler<MemberEventArgs> MemberAdded; | |||
| private void RaiseMemberAdded(Membership membership, Server server) | |||
| private void RaiseMemberAdded(Membership membership) | |||
| { | |||
| if (MemberAdded != null) | |||
| MemberAdded(this, new MemberEventArgs(membership)); | |||
| } | |||
| public event EventHandler<MemberEventArgs> MemberRemoved; | |||
| private void RaiseMemberRemoved(Membership membership, Server server) | |||
| private void RaiseMemberRemoved(Membership membership) | |||
| { | |||
| if (MemberRemoved != null) | |||
| MemberRemoved(this, new MemberEventArgs(membership)); | |||
| } | |||
| public event EventHandler<MemberEventArgs> MemberUpdated; | |||
| private void RaiseMemberUpdated(Membership membership, Server server) | |||
| private void RaiseMemberUpdated(Membership membership) | |||
| { | |||
| if (MemberUpdated != null) | |||
| MemberUpdated(this, new MemberEventArgs(membership)); | |||
| @@ -217,11 +217,11 @@ namespace Discord | |||
| } | |||
| } | |||
| public event EventHandler<UserEventArgs> PresenceUpdated; | |||
| private void RaisePresenceUpdated(User user) | |||
| public event EventHandler<MemberEventArgs> PresenceUpdated; | |||
| private void RaisePresenceUpdated(Membership member) | |||
| { | |||
| if (PresenceUpdated != null) | |||
| PresenceUpdated(this, new UserEventArgs(user)); | |||
| PresenceUpdated(this, new MemberEventArgs(member)); | |||
| } | |||
| public event EventHandler<MemberEventArgs> VoiceStateUpdated; | |||
| private void RaiseVoiceStateUpdated(Membership member) | |||
| @@ -1,6 +1,7 @@ | |||
| using Discord.API; | |||
| using Discord.API.Models; | |||
| using Discord.Helpers; | |||
| using Newtonsoft.Json; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.IO; | |||
| @@ -19,10 +20,13 @@ namespace Discord | |||
| private ManualResetEventSlim _isStopping; | |||
| private readonly Regex _userRegex, _channelRegex; | |||
| private readonly MatchEvaluator _userRegexEvaluator, _channelRegexEvaluator; | |||
| private readonly JsonSerializer _serializer; | |||
| /// <summary> Returns the User object for the current logged in user. </summary> | |||
| 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 SessionId { get; private set; } | |||
| /// <summary> Returns a collection of all users the client can see across all servers. </summary> | |||
| /// <remarks> This collection does not guarantee any ordering. </remarks> | |||
| @@ -64,6 +68,12 @@ namespace Discord | |||
| { | |||
| _isStopping = new ManualResetEventSlim(false); | |||
| _serializer = new JsonSerializer(); | |||
| #if TEST_RESPONSES | |||
| _serializer.CheckAdditionalContent = true; | |||
| _serializer.MissingMemberHandling = MissingMemberHandling.Error; | |||
| #endif | |||
| _userRegex = new Regex(@"<@\d+?>", RegexOptions.Compiled); | |||
| _channelRegex = new Regex(@"<#\d+?>", RegexOptions.Compiled); | |||
| _userRegexEvaluator = new MatchEvaluator(e => | |||
| @@ -90,11 +100,7 @@ namespace Discord | |||
| (server, model) => | |||
| { | |||
| server.Name = model.Name; | |||
| if (!server.Channels.Any()) //A default channel always exists with the same id as the server. | |||
| { | |||
| var defaultChannel = new ChannelReference() { Id = server.DefaultChannelId, GuildId = server.Id }; | |||
| _channels.Update(defaultChannel.Id, defaultChannel.GuildId, defaultChannel); | |||
| } | |||
| _channels.Update(server.DefaultChannelId, server.Id, null); | |||
| if (model is ExtendedServerInfo) | |||
| { | |||
| var extendedModel = model as ExtendedServerInfo; | |||
| @@ -102,9 +108,7 @@ namespace Discord | |||
| server.AFKTimeout = extendedModel.AFKTimeout; | |||
| server.JoinedAt = extendedModel.JoinedAt ?? DateTime.MinValue; | |||
| server.OwnerId = extendedModel.OwnerId; | |||
| server.Presence = extendedModel.Presence; | |||
| server.Region = extendedModel.Region; | |||
| server.VoiceStates = extendedModel.VoiceStates; | |||
| foreach (var role in extendedModel.Roles) | |||
| _roles.Update(role.Id, model.Id, role); | |||
| @@ -113,11 +117,13 @@ namespace Discord | |||
| foreach (var membership in extendedModel.Members) | |||
| { | |||
| _users.Update(membership.User.Id, membership.User); | |||
| var newMember = new Membership(server.Id, membership.User.Id, membership.JoinedAt, this); | |||
| newMember.Update(membership); | |||
| server.AddMember(newMember); | |||
| server.UpdateMember(membership); | |||
| } | |||
| } | |||
| foreach (var membership in extendedModel.VoiceStates) | |||
| server.UpdateMember(membership); | |||
| foreach (var membership in extendedModel.Presences) | |||
| server.UpdateMember(membership); | |||
| } | |||
| }, | |||
| server => { } | |||
| ); | |||
| @@ -131,13 +137,27 @@ namespace Discord | |||
| if (model is ChannelInfo) | |||
| { | |||
| var extendedModel = model as ChannelInfo; | |||
| channel.PermissionOverwrites = extendedModel.PermissionOverwrites; | |||
| channel.Position = extendedModel.Position; | |||
| if (extendedModel.IsPrivate) | |||
| { | |||
| var user = _users.Update(extendedModel.Recipient.Id, extendedModel.Recipient); | |||
| channel.RecipientId = user.Id; | |||
| channel.RecipientId = user.Id; | |||
| user.PrivateChannelId = channel.Id; | |||
| } | |||
| if (extendedModel.PermissionOverwrites != null) | |||
| { | |||
| channel.PermissionOverwrites = extendedModel.PermissionOverwrites.Select(x => new Channel.PermissionOverwrite | |||
| { | |||
| Type = x.Type, | |||
| Id = x.Id, | |||
| Deny = new PackedPermissions(x.Deny), | |||
| Allow = new PackedPermissions(x.Allow) | |||
| }).ToArray(); | |||
| } | |||
| else | |||
| channel.PermissionOverwrites = null; | |||
| } | |||
| }, | |||
| channel => | |||
| @@ -170,8 +190,41 @@ namespace Discord | |||
| }).ToArray(); | |||
| } | |||
| else | |||
| extendedModel.Attachments = null; | |||
| message.Embeds = extendedModel.Embeds; | |||
| message.Attachments = new Message.Attachment[0]; | |||
| if (extendedModel.Embeds != null) | |||
| { | |||
| message.Embeds = extendedModel.Embeds.Select(x => | |||
| { | |||
| var embed = new Message.Embed | |||
| { | |||
| Url = x.Url, | |||
| Type = x.Type, | |||
| Description = x.Description, | |||
| Title = x.Title | |||
| }; | |||
| if (x.Provider != null) | |||
| { | |||
| embed.Provider = new Message.EmbedProvider | |||
| { | |||
| Url = x.Provider.Url, | |||
| Name = x.Provider.Name | |||
| }; | |||
| } | |||
| if (x.Thumbnail != null) | |||
| { | |||
| embed.Thumbnail = new Message.File | |||
| { | |||
| Url = x.Thumbnail.Url, | |||
| ProxyUrl = x.Thumbnail.ProxyUrl, | |||
| Width = x.Thumbnail.Width, | |||
| Height = x.Thumbnail.Height | |||
| }; | |||
| } | |||
| return embed; | |||
| }).ToArray(); | |||
| } | |||
| else | |||
| message.Embeds = new Message.Embed[0]; | |||
| message.IsMentioningEveryone = extendedModel.IsMentioningEveryone; | |||
| message.IsTTS = extendedModel.IsTextToSpeech; | |||
| message.MentionIds = extendedModel.Mentions?.Select(x => x.Id)?.ToArray() ?? new string[0]; | |||
| @@ -206,12 +259,6 @@ namespace Discord | |||
| var extendedModel = model as SelfUserInfo; | |||
| user.Email = extendedModel.Email; | |||
| user.IsVerified = extendedModel.IsVerified; | |||
| } | |||
| if (model is PresenceUserInfo) | |||
| { | |||
| var extendedModel = model as PresenceUserInfo; | |||
| user.GameId = extendedModel.GameId; | |||
| user.Status = extendedModel.Status; | |||
| } | |||
| }, | |||
| user => { } | |||
| @@ -246,101 +293,104 @@ namespace Discord | |||
| //Global | |||
| case "READY": //Resync | |||
| { | |||
| var data = e.Event.ToObject<WebSocketEvents.Ready>(); | |||
| var data = e.Event.ToObject<WebSocketEvents.Ready>(_serializer); | |||
| _servers.Clear(); | |||
| _channels.Clear(); | |||
| _users.Clear(); | |||
| UserId = data.User.Id; | |||
| SessionId = data.SessionId; | |||
| User = _users.Update(data.User.Id, data.User); | |||
| foreach (var server in data.Guilds) | |||
| _servers.Update(server.Id, server); | |||
| foreach (var channel in data.PrivateChannels) | |||
| _channels.Update(channel.Id, null, channel); | |||
| } | |||
| } | |||
| break; | |||
| //Servers | |||
| case "GUILD_CREATE": | |||
| { | |||
| var data = e.Event.ToObject<WebSocketEvents.GuildCreate>(); | |||
| var data = e.Event.ToObject<WebSocketEvents.GuildCreate>(_serializer); | |||
| var server = _servers.Update(data.Id, data); | |||
| RaiseServerCreated(server); | |||
| try { RaiseServerCreated(server); } catch { } | |||
| } | |||
| break; | |||
| 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); | |||
| RaiseServerUpdated(server); | |||
| try { RaiseServerUpdated(server); } catch { } | |||
| } | |||
| break; | |||
| case "GUILD_DELETE": | |||
| { | |||
| var data = e.Event.ToObject<WebSocketEvents.GuildDelete>(); | |||
| var data = e.Event.ToObject<WebSocketEvents.GuildDelete>(_serializer); | |||
| var server = _servers.Remove(data.Id); | |||
| if (server != null) | |||
| RaiseServerDestroyed(server); | |||
| try { RaiseServerDestroyed(server); } catch { } | |||
| } | |||
| break; | |||
| //Channels | |||
| case "CHANNEL_CREATE": | |||
| { | |||
| var data = e.Event.ToObject<WebSocketEvents.ChannelCreate>(); | |||
| var data = e.Event.ToObject<WebSocketEvents.ChannelCreate>(_serializer); | |||
| var channel = _channels.Update(data.Id, data.GuildId, data); | |||
| RaiseChannelCreated(channel); | |||
| try { RaiseChannelCreated(channel); } catch { } | |||
| } | |||
| break; | |||
| case "CHANNEL_UPDATE": | |||
| { | |||
| var data = e.Event.ToObject<WebSocketEvents.ChannelUpdate>(); | |||
| var data = e.Event.ToObject<WebSocketEvents.ChannelUpdate>(_serializer); | |||
| var channel = _channels.Update(data.Id, data.GuildId, data); | |||
| RaiseChannelUpdated(channel); | |||
| try { RaiseChannelUpdated(channel); } catch { } | |||
| } | |||
| break; | |||
| case "CHANNEL_DELETE": | |||
| { | |||
| var data = e.Event.ToObject<WebSocketEvents.ChannelDelete>(); | |||
| var data = e.Event.ToObject<WebSocketEvents.ChannelDelete>(_serializer); | |||
| var channel = _channels.Remove(data.Id); | |||
| if (channel != null) | |||
| RaiseChannelDestroyed(channel); | |||
| try { RaiseChannelDestroyed(channel); } catch { } | |||
| } | |||
| break; | |||
| //Members | |||
| case "GUILD_MEMBER_ADD": | |||
| { | |||
| var data = e.Event.ToObject<WebSocketEvents.GuildMemberAdd>(); | |||
| var data = e.Event.ToObject<WebSocketEvents.GuildMemberAdd>(_serializer); | |||
| var user = _users.Update(data.User.Id, data.User); | |||
| var server = _servers[data.GuildId]; | |||
| var membership = new Membership(server.Id, data.User.Id, data.JoinedAt, this) { RoleIds = data.Roles }; | |||
| server.AddMember(membership); | |||
| RaiseMemberAdded(membership, server); | |||
| var server = _servers[data.ServerId]; | |||
| if (server != null) | |||
| { | |||
| var member = server.UpdateMember(data); | |||
| try { RaiseMemberAdded(member); } catch { } | |||
| } | |||
| } | |||
| break; | |||
| case "GUILD_MEMBER_UPDATE": | |||
| { | |||
| var data = e.Event.ToObject<WebSocketEvents.GuildMemberUpdate>(); | |||
| var data = e.Event.ToObject<WebSocketEvents.GuildMemberUpdate>(_serializer); | |||
| var user = _users.Update(data.User.Id, data.User); | |||
| var server = _servers[data.GuildId]; | |||
| var membership = server.GetMember(data.User.Id); | |||
| if (membership != null) | |||
| membership.RoleIds = data.Roles; | |||
| RaiseMemberUpdated(membership, server); | |||
| var server = _servers[data.ServerId]; | |||
| if (server != null) | |||
| { | |||
| var member = server.UpdateMember(data); | |||
| try { RaiseMemberUpdated(member); } catch { } | |||
| } | |||
| } | |||
| break; | |||
| case "GUILD_MEMBER_REMOVE": | |||
| { | |||
| var data = e.Event.ToObject<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) | |||
| { | |||
| var membership = server.RemoveMember(user.Id); | |||
| if (membership != null) | |||
| RaiseMemberRemoved(membership, server); | |||
| var member = server.RemoveMember(data.UserId); | |||
| if (member != null) | |||
| try { RaiseMemberRemoved(member); } catch { } | |||
| } | |||
| } | |||
| break; | |||
| @@ -348,121 +398,138 @@ namespace Discord | |||
| //Roles | |||
| case "GUILD_ROLE_CREATE": | |||
| { | |||
| var data = e.Event.ToObject<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; | |||
| 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; | |||
| 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); | |||
| if (role != null) | |||
| RaiseRoleDeleted(role); | |||
| try { RaiseRoleDeleted(role); } catch { } | |||
| } | |||
| break; | |||
| //Bans | |||
| 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 server = _servers[data.GuildId]; | |||
| RaiseBanAdded(user, server); | |||
| var server = _servers[data.ServerId]; | |||
| try { RaiseBanAdded(user, server); } catch { } | |||
| } | |||
| break; | |||
| case "GUILD_BAN_REMOVE": | |||
| { | |||
| var data = e.Event.ToObject<WebSocketEvents.GuildBanAddRemove>(); | |||
| var data = e.Event.ToObject<WebSocketEvents.GuildBanAddRemove>(_serializer); | |||
| var user = _users.Update(data.User.Id, data.User); | |||
| var server = _servers[data.GuildId]; | |||
| var server = _servers[data.ServerId]; | |||
| if (server != null && server.RemoveBan(user.Id)) | |||
| RaiseBanRemoved(user, server); | |||
| { | |||
| try { RaiseBanRemoved(user, server); } catch { } | |||
| } | |||
| } | |||
| break; | |||
| //Messages | |||
| case "MESSAGE_CREATE": | |||
| { | |||
| var data = e.Event.ToObject<WebSocketEvents.MessageCreate>(); | |||
| var data = e.Event.ToObject<WebSocketEvents.MessageCreate>(_serializer); | |||
| var msg = _messages.Update(data.Id, data.ChannelId, data); | |||
| msg.User.UpdateActivity(data.Timestamp); | |||
| RaiseMessageCreated(msg); | |||
| try { RaiseMessageCreated(msg); } catch { } | |||
| } | |||
| break; | |||
| case "MESSAGE_UPDATE": | |||
| { | |||
| var data = e.Event.ToObject<WebSocketEvents.MessageUpdate>(); | |||
| var data = e.Event.ToObject<WebSocketEvents.MessageUpdate>(_serializer); | |||
| var msg = _messages.Update(data.Id, data.ChannelId, data); | |||
| RaiseMessageUpdated(msg); | |||
| try { RaiseMessageUpdated(msg); } catch { } | |||
| } | |||
| break; | |||
| case "MESSAGE_DELETE": | |||
| { | |||
| var data = e.Event.ToObject<WebSocketEvents.MessageDelete>(); | |||
| var data = e.Event.ToObject<WebSocketEvents.MessageDelete>(_serializer); | |||
| var msg = GetMessage(data.MessageId); | |||
| if (msg != null) | |||
| { | |||
| _messages.Remove(msg.Id); | |||
| try { RaiseMessageDeleted(msg); } catch { } | |||
| } | |||
| } | |||
| break; | |||
| case "MESSAGE_ACK": | |||
| { | |||
| var data = e.Event.ToObject<WebSocketEvents.MessageAck>(); | |||
| var data = e.Event.ToObject<WebSocketEvents.MessageAck>(_serializer); | |||
| var msg = GetMessage(data.MessageId); | |||
| RaiseMessageAcknowledged(msg); | |||
| if (msg != null) | |||
| try { RaiseMessageAcknowledged(msg); } catch { } | |||
| } | |||
| break; | |||
| //Statuses | |||
| case "PRESENCE_UPDATE": | |||
| { | |||
| var data = e.Event.ToObject<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; | |||
| 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; | |||
| case "TYPING_START": | |||
| { | |||
| var data = e.Event.ToObject<WebSocketEvents.TypingStart>(); | |||
| var data = e.Event.ToObject<WebSocketEvents.TypingStart>(_serializer); | |||
| var channel = _channels[data.ChannelId]; | |||
| var user = _users[data.UserId]; | |||
| RaiseUserTyping(user, channel); | |||
| if (user != null) | |||
| { | |||
| user.UpdateActivity(DateTime.UtcNow); | |||
| if (channel != null) | |||
| try { RaiseUserTyping(user, channel); } catch { } | |||
| } | |||
| } | |||
| break; | |||
| //Voice | |||
| case "VOICE_SERVER_UPDATE": | |||
| { | |||
| var data = e.Event.ToObject<WebSocketEvents.VoiceServerUpdate>(); | |||
| var data = e.Event.ToObject<WebSocketEvents.VoiceServerUpdate>(_serializer); | |||
| var server = _servers[data.ServerId]; | |||
| RaiseVoiceServerUpdated(server, data.Endpoint); | |||
| try { RaiseVoiceServerUpdated(server, data.Endpoint); } catch { } | |||
| } | |||
| break; | |||
| //Settings | |||
| case "USER_UPDATE": | |||
| { | |||
| var data = e.Event.ToObject<WebSocketEvents.UserUpdate>(); | |||
| var data = e.Event.ToObject<WebSocketEvents.UserUpdate>(_serializer); | |||
| var user = _users.Update(data.Id, data); | |||
| RaiseUserUpdated(user); | |||
| try { RaiseUserUpdated(user); } catch { } | |||
| } | |||
| break; | |||
| case "USER_SETTINGS_UPDATE": | |||
| @@ -98,7 +98,8 @@ namespace Discord | |||
| { | |||
| throw new InvalidOperationException("Bad Token"); | |||
| } | |||
| _connectWaitOnLogin2.Wait(cancelToken); //Waiting on READY handler | |||
| try { _connectWaitOnLogin2.Wait(cancelToken); } //Waiting on READY handler | |||
| catch (OperationCanceledException) { return; } | |||
| _isConnected = true; | |||
| RaiseConnected(); | |||
| @@ -150,11 +151,7 @@ namespace Discord | |||
| QueueMessage(new WebSocketCommands.KeepAlive()); | |||
| _connectWaitOnLogin.Set(); //Pre-Event | |||
| } | |||
| try | |||
| { | |||
| RaiseGotEvent(msg.Type, msg.Payload as JToken); | |||
| } | |||
| catch { } //Don't allow user exceptions to affect our state | |||
| RaiseGotEvent(msg.Type, msg.Payload as JToken); | |||
| if (msg.Type == "READY") | |||
| _connectWaitOnLogin2.Set(); //Post-Event | |||
| break; | |||
| @@ -14,7 +14,7 @@ namespace Discord.Helpers | |||
| private readonly Action<TValue, TModel> _onUpdate; | |||
| 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>(); | |||
| _onCreate = onCreate; | |||
| @@ -49,7 +49,8 @@ namespace Discord.Helpers | |||
| isNew = !_dictionary.TryGetValue(key, out value); | |||
| if (isNew) | |||
| value = _onCreate(key, parentKey); | |||
| _onUpdate(value, model); | |||
| if (model != null) | |||
| _onUpdate(value, model); | |||
| if (isNew) | |||
| { | |||
| //If this fails, repeat as an update instead of an add | |||
| @@ -68,7 +69,11 @@ namespace Discord.Helpers | |||
| { | |||
| TValue value = null; | |||
| if (_dictionary.TryRemove(key, out value)) | |||
| { | |||
| if (_onRemove != null) | |||
| _onRemove(value); | |||
| return value; | |||
| } | |||
| else | |||
| return null; | |||
| } | |||
| @@ -10,7 +10,6 @@ using System.Globalization; | |||
| #if TEST_RESPONSES | |||
| using System.Diagnostics; | |||
| using JsonErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; | |||
| #endif | |||
| namespace Discord.Helpers | |||
| @@ -18,6 +18,11 @@ namespace Discord | |||
| public bool IsSuppressed { 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 Server Server => _client.GetServer(ServerId); | |||
| @@ -31,29 +36,11 @@ namespace Discord | |||
| public string[] RoleIds { get; internal set; } | |||
| 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; | |||
| UserId = userId; | |||
| JoinedAt = joinedAt; | |||
| _client = client; | |||
| } | |||
| internal void Update(ExtendedServerInfo.Membership data) | |||
| { | |||
| IsDeafened = data.IsDeaf; | |||
| IsMuted = data.IsMuted; | |||
| RoleIds = data.Roles; | |||
| } | |||
| internal void Update(WebSocketEvents.VoiceStateUpdate data) | |||
| { | |||
| VoiceChannelId = data.ChannelId; | |||
| IsDeafened = data.IsDeafened; | |||
| IsMuted = data.IsMuted; | |||
| IsSelfDeafened = data.IsSelfDeafened; | |||
| IsSelfMuted = data.IsSelfMuted; | |||
| IsSuppressed = data.IsSuppressed; | |||
| SessionId = data.SessionId; | |||
| } | |||
| } | |||
| } | |||
| @@ -7,10 +7,39 @@ namespace Discord | |||
| { | |||
| public sealed class Message | |||
| { | |||
| public class Attachment | |||
| public sealed class Attachment : File | |||
| { | |||
| /// <summary> Unique identifier for this file. </summary> | |||
| 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> | |||
| public string Url { get; internal set; } | |||
| /// <summary> Preview url for this file. </summary> | |||
| @@ -19,10 +48,6 @@ namespace Discord | |||
| public int? Width { get; internal set; } | |||
| /// <summary> Height of this file, if it is an image. </summary> | |||
| 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; | |||
| @@ -49,6 +74,9 @@ namespace Discord | |||
| public DateTime? EditedTimestamp { get; internal set; } | |||
| /// <summary> Returns the attachments included in this message. </summary> | |||
| 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> | |||
| public string[] MentionIds { get; internal set; } | |||
| @@ -68,10 +96,6 @@ namespace Discord | |||
| [JsonIgnore] | |||
| 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) | |||
| { | |||
| 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 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; | |||
| /// <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.Generic; | |||
| using System.Linq; | |||
| @@ -38,9 +39,9 @@ namespace Discord | |||
| /// <summary> Returns the default channel for this server. </summary> | |||
| 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> | |||
| public IEnumerable<Membership> Members => _members.Values; | |||
| public IEnumerable<Membership> Members => _members; | |||
| internal ConcurrentDictionary<string, bool> _bans; | |||
| /// <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> | |||
| 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) | |||
| { | |||
| Id = id; | |||
| _client = client; | |||
| _members = new ConcurrentDictionary<string, Membership>(); | |||
| _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) | |||
| { | |||
| Membership result = null; | |||
| _members.TryRemove(userId, out result); | |||
| return result; | |||
| return _members.Remove(userId); | |||
| } | |||
| public Membership GetMembership(User user) | |||
| => GetMember(user.Id); | |||
| public Membership GetMember(string userId) | |||
| { | |||
| Membership result = null; | |||
| _members.TryGetValue(userId, out result); | |||
| return result; | |||
| return _members[userId]; | |||
| } | |||
| internal void AddBan(string banId) | |||
| @@ -27,10 +27,6 @@ namespace Discord | |||
| /// <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> | |||
| 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> | |||
| public string Mention { get { return $"<@{Id}>"; } } | |||