| @@ -68,7 +68,7 @@ namespace Discord.Commands | |||||
| client.MessageReceived += async (s, e) => | client.MessageReceived += async (s, e) => | ||||
| { | { | ||||
| if (_allCommands.Count == 0) return; | if (_allCommands.Count == 0) return; | ||||
| if (e.Message.User.Id == _client.CurrentUser.Id) return; | |||||
| if (e.Message.User == null || e.Message.User.Id == _client.CurrentUser.Id) return; | |||||
| string msg = e.Message.RawText; | string msg = e.Message.RawText; | ||||
| if (msg.Length == 0) return; | if (msg.Length == 0) return; | ||||
| @@ -9,8 +9,7 @@ namespace Discord.API.Client | |||||
| { | { | ||||
| [JsonProperty("type")] | [JsonProperty("type")] | ||||
| public string Type { get; set; } | public string Type { get; set; } | ||||
| [JsonProperty("id")] | |||||
| [JsonConverter(typeof(LongStringConverter))] | |||||
| [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] | |||||
| public ulong Id { get; set; } | public ulong Id { get; set; } | ||||
| [JsonProperty("deny")] | [JsonProperty("deny")] | ||||
| public uint Deny { get; set; } | public uint Deny { get; set; } | ||||
| @@ -7,8 +7,8 @@ namespace Discord.API.Client | |||||
| { | { | ||||
| [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] | [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] | ||||
| public ulong Id { get; set; } | public ulong Id { get; set; } | ||||
| [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | |||||
| public ulong GuildId { get; set; } | |||||
| [JsonProperty("guild_id"), JsonConverter(typeof(NullableLongStringConverter))] | |||||
| public ulong? GuildId { get; set; } | |||||
| [JsonProperty("name")] | [JsonProperty("name")] | ||||
| public string Name { get; set; } | public string Name { get; set; } | ||||
| [JsonProperty("type")] | [JsonProperty("type")] | ||||
| @@ -5,7 +5,7 @@ namespace Discord.API.Client | |||||
| { | { | ||||
| public class MemberReference | public class MemberReference | ||||
| { | { | ||||
| [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] | |||||
| [JsonProperty("guild_id"), JsonConverter(typeof(NullableLongStringConverter))] | |||||
| public ulong? GuildId { get; set; } | public ulong? GuildId { get; set; } | ||||
| [JsonProperty("user")] | [JsonProperty("user")] | ||||
| public UserReference User { get; set; } | public UserReference User { get; set; } | ||||
| @@ -171,10 +171,7 @@ namespace Discord | |||||
| State = ConnectionState.Connecting; | State = ConnectionState.Connecting; | ||||
| _disconnectedEvent.Reset(); | _disconnectedEvent.Reset(); | ||||
| await Login(email, password, token).ConfigureAwait(false); | |||||
| ClientAPI.Token = token; | |||||
| GatewaySocket.Token = token; | |||||
| await Login(email, password, token).ConfigureAwait(false); | |||||
| await GatewaySocket.Connect().ConfigureAwait(false); | await GatewaySocket.Connect().ConfigureAwait(false); | ||||
| List<Task> tasks = new List<Task>(); | List<Task> tasks = new List<Task>(); | ||||
| @@ -233,6 +230,9 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| ClientAPI.Token = token; | |||||
| GatewaySocket.Token = token; | |||||
| //Get gateway and check token | //Get gateway and check token | ||||
| try | try | ||||
| { | { | ||||
| @@ -309,22 +309,10 @@ namespace Discord | |||||
| } | } | ||||
| #region Channels | #region Channels | ||||
| private Channel AddChannel(ulong id, ulong? guildId, ulong? recipientId) | |||||
| internal void AddChannel(Channel channel) | |||||
| { | { | ||||
| Channel channel; | |||||
| if (recipientId != null) | |||||
| { | |||||
| channel = _privateChannels.GetOrAdd(recipientId.Value, | |||||
| x => new Channel(this, x, new User(this, recipientId.Value, null))); | |||||
| } | |||||
| else | |||||
| { | |||||
| var server = GetServer(guildId.Value); | |||||
| channel = server.AddChannel(id); | |||||
| } | |||||
| _channels[channel.Id] = channel; | _channels[channel.Id] = channel; | ||||
| return channel; | |||||
| } | |||||
| } | |||||
| private Channel RemoveChannel(ulong id) | private Channel RemoveChannel(ulong id) | ||||
| { | { | ||||
| Channel channel; | Channel channel; | ||||
| @@ -337,20 +325,26 @@ namespace Discord | |||||
| } | } | ||||
| return channel; | return channel; | ||||
| } | } | ||||
| internal Channel GetChannel(ulong id) | |||||
| public Channel GetChannel(ulong id) | |||||
| { | { | ||||
| Channel channel; | Channel channel; | ||||
| _channels.TryGetValue(id, out channel); | _channels.TryGetValue(id, out channel); | ||||
| return channel; | return channel; | ||||
| } | } | ||||
| private Channel AddPrivateChannel(ulong id, ulong recipientId) | |||||
| { | |||||
| Channel channel; | |||||
| if (_privateChannels.TryGetOrAdd(recipientId, x => new Channel(this, id, new User(this, x, null)), out channel)) | |||||
| AddChannel(channel); | |||||
| return channel; | |||||
| } | |||||
| internal Channel GetPrivateChannel(ulong recipientId) | internal Channel GetPrivateChannel(ulong recipientId) | ||||
| { | { | ||||
| Channel channel; | Channel channel; | ||||
| _privateChannels.TryGetValue(recipientId, out channel); | _privateChannels.TryGetValue(recipientId, out channel); | ||||
| return channel; | return channel; | ||||
| } | } | ||||
| internal async Task<Channel> CreatePrivateChannel(User user) | internal async Task<Channel> CreatePrivateChannel(User user) | ||||
| { | { | ||||
| var channel = GetPrivateChannel(user.Id); | var channel = GetPrivateChannel(user.Id); | ||||
| @@ -358,8 +352,8 @@ namespace Discord | |||||
| var request = new CreatePrivateChannelRequest() { RecipientId = user.Id }; | var request = new CreatePrivateChannelRequest() { RecipientId = user.Id }; | ||||
| var response = await ClientAPI.Send(request).ConfigureAwait(false); | var response = await ClientAPI.Send(request).ConfigureAwait(false); | ||||
| channel = AddChannel(response.Id, null, response.Recipient.Id); | |||||
| channel = AddPrivateChannel(response.Id, user.Id); | |||||
| channel.Update(response); | channel.Update(response); | ||||
| return channel; | return channel; | ||||
| } | } | ||||
| @@ -453,6 +447,7 @@ namespace Discord | |||||
| SessionId = data.SessionId; | SessionId = data.SessionId; | ||||
| PrivateUser = new User(this, data.User.Id, null); | PrivateUser = new User(this, data.User.Id, null); | ||||
| PrivateUser.Update(data.User); | PrivateUser.Update(data.User); | ||||
| CurrentUser = new Profile(this, data.User.Id); | |||||
| CurrentUser.Update(data.User); | CurrentUser.Update(data.User); | ||||
| foreach (var model in data.Guilds) | foreach (var model in data.Guilds) | ||||
| { | { | ||||
| @@ -464,7 +459,7 @@ namespace Discord | |||||
| } | } | ||||
| foreach (var model in data.PrivateChannels) | foreach (var model in data.PrivateChannels) | ||||
| { | { | ||||
| var channel = AddChannel(model.Id, null, model.Recipient.Id); | |||||
| var channel = AddPrivateChannel(model.Id, model.Recipient.Id); | |||||
| channel.Update(model); | channel.Update(model); | ||||
| } | } | ||||
| } | } | ||||
| @@ -523,10 +518,22 @@ namespace Discord | |||||
| case "CHANNEL_CREATE": | case "CHANNEL_CREATE": | ||||
| { | { | ||||
| var data = e.Payload.ToObject<ChannelCreateEvent>(_serializer); | var data = e.Payload.ToObject<ChannelCreateEvent>(_serializer); | ||||
| Channel channel = AddChannel(data.Id, data.GuildId, data.Recipient.Id); | |||||
| channel.Update(data); | |||||
| Logger.Info($"Channel Created: {channel.Server?.Name ?? "[Private]"}/{channel.Name}"); | |||||
| OnChannelCreated(channel); | |||||
| Channel channel = null; | |||||
| if (data.GuildId != null) | |||||
| { | |||||
| var server = GetServer(data.GuildId.Value); | |||||
| if (server != null) | |||||
| channel = server.AddChannel(data.Id); | |||||
| } | |||||
| else | |||||
| channel = AddPrivateChannel(data.Id, data.Recipient.Id); | |||||
| if (channel != null) | |||||
| { | |||||
| channel.Update(data); | |||||
| Logger.Info($"Channel Created: {channel.Server?.Name ?? "[Private]"}/{channel.Name}"); | |||||
| OnChannelCreated(channel); | |||||
| } | |||||
| } | } | ||||
| break; | break; | ||||
| case "CHANNEL_UPDATE": | case "CHANNEL_UPDATE": | ||||
| @@ -27,15 +27,22 @@ namespace Discord | |||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| public static bool HasBit(this uint value, byte bit) => ((value >> bit) & 1U) == 1; | public static bool HasBit(this uint value, byte bit) => ((value >> bit) & 1U) == 1; | ||||
| public static bool TryGetAdd<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> d, | |||||
| public static bool TryGetOrAdd<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> d, | |||||
| TKey key, Func<TKey, TValue> factory, out TValue result) | TKey key, Func<TKey, TValue> factory, out TValue result) | ||||
| where TValue : class | |||||
| { | { | ||||
| TValue newValue = null; | |||||
| while (true) | while (true) | ||||
| { | { | ||||
| if (d.TryGetValue(key, out result)) | if (d.TryGetValue(key, out result)) | ||||
| return false; | return false; | ||||
| if (d.TryAdd(key, factory(key))) | |||||
| if (newValue == null) | |||||
| newValue = factory(key); | |||||
| if (d.TryAdd(key, newValue)) | |||||
| { | |||||
| result = newValue; | |||||
| return true; | return true; | ||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -112,7 +112,6 @@ namespace Discord | |||||
| : this(client, id) | : this(client, id) | ||||
| { | { | ||||
| Recipient = recipient; | Recipient = recipient; | ||||
| Name = $"@{recipient}"; | |||||
| AddUser(client.PrivateUser); | AddUser(client.PrivateUser); | ||||
| AddUser(recipient); | AddUser(recipient); | ||||
| } | } | ||||
| @@ -143,7 +142,10 @@ namespace Discord | |||||
| if (model.Topic != null) | if (model.Topic != null) | ||||
| Topic = model.Topic; | Topic = model.Topic; | ||||
| if (model.Recipient != null) | if (model.Recipient != null) | ||||
| { | |||||
| Recipient.Update(model.Recipient); | Recipient.Update(model.Recipient); | ||||
| Name = $"@{Recipient}"; | |||||
| } | |||||
| if (model.PermissionOverwrites != null) | if (model.PermissionOverwrites != null) | ||||
| { | { | ||||
| @@ -263,9 +265,13 @@ namespace Discord | |||||
| public Message GetMessage(ulong id) | public Message GetMessage(ulong id) | ||||
| { | { | ||||
| Message result; | |||||
| _messages.TryGetValue(id, out result); | |||||
| return result; | |||||
| if (Client.Config.MessageCacheSize > 0) | |||||
| { | |||||
| Message result; | |||||
| _messages.TryGetValue(id, out result); | |||||
| return result; | |||||
| } | |||||
| return null; | |||||
| } | } | ||||
| public async Task<Message[]> DownloadMessages(int limit = 100, ulong? relativeMessageId = null, | public async Task<Message[]> DownloadMessages(int limit = 100, ulong? relativeMessageId = null, | ||||
| RelativeDirection relativeDir = RelativeDirection.Before, bool useCache = true) | RelativeDirection relativeDir = RelativeDirection.Before, bool useCache = true) | ||||
| @@ -556,6 +562,17 @@ namespace Discord | |||||
| } | } | ||||
| public User GetUser(ulong id) | public User GetUser(ulong id) | ||||
| { | { | ||||
| if (!Client.Config.UsePermissionsCache) | |||||
| { | |||||
| var user = Server.GetUser(id); | |||||
| ChannelPermissions perms = new ChannelPermissions(); | |||||
| UpdatePermissions(user, perms); | |||||
| if (perms.ReadMessages) | |||||
| return user; | |||||
| else | |||||
| return null; | |||||
| } | |||||
| Member result; | Member result; | ||||
| _users.TryGetValue(id, out result); | _users.TryGetValue(id, out result); | ||||
| return result.User; | return result.User; | ||||
| @@ -28,7 +28,6 @@ namespace Discord | |||||
| public static readonly Color LightGrey = PresetColor(0x979C9F); | public static readonly Color LightGrey = PresetColor(0x979C9F); | ||||
| public static readonly Color DarkerGrey = PresetColor(0x546E7A); | public static readonly Color DarkerGrey = PresetColor(0x546E7A); | ||||
| private static Color PresetColor(uint packedValue) | private static Color PresetColor(uint packedValue) | ||||
| { | { | ||||
| Color color = new Color(packedValue); | Color color = new Color(packedValue); | ||||
| @@ -1,79 +0,0 @@ | |||||
| using Newtonsoft.Json; | |||||
| using System.Collections.Concurrent; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using APIUser = Discord.API.Client.User; | |||||
| namespace Discord | |||||
| { | |||||
| /*public sealed class GlobalUser : CachedObject<ulong> | |||||
| { | |||||
| /// <summary> Returns the email for this user. Note: this field is only ever populated for the current logged in user. </summary> | |||||
| [JsonIgnore] | |||||
| public string Email { get; private set; } | |||||
| /// <summary> Returns if the email for this user has been verified. Note: this field is only ever populated for the current logged in user. </summary> | |||||
| [JsonIgnore] | |||||
| public bool? IsVerified { get; private set; } | |||||
| /// <summary> Returns the private messaging channel with this user, if one exists. </summary> | |||||
| [JsonIgnore] | |||||
| public Channel PrivateChannel | |||||
| { | |||||
| get { return _privateChannel; } | |||||
| set | |||||
| { | |||||
| _privateChannel = value; | |||||
| if (value == null) | |||||
| CheckUser(); | |||||
| } | |||||
| } | |||||
| [JsonProperty] | |||||
| private ulong? PrivateChannelId => _privateChannel?.Id; | |||||
| private Channel _privateChannel; | |||||
| /// <summary> Returns a collection of all server-specific data for every server this user is a member of. </summary> | |||||
| [JsonIgnore] | |||||
| public IEnumerable<User> Memberships => _users.Select(x => x.Value); | |||||
| [JsonProperty] | |||||
| private IEnumerable<ulong> ServerIds => _users.Select(x => x.Key); | |||||
| private readonly ConcurrentDictionary<ulong, User> _users; | |||||
| /// <summary> Returns the string used to mention this user. </summary> | |||||
| public string Mention => $"<@{Id}>"; | |||||
| internal GlobalUser(DiscordClient client, ulong id) | |||||
| : base(client, id) | |||||
| { | |||||
| _users = new ConcurrentDictionary<ulong, User>(); | |||||
| } | |||||
| internal override bool LoadReferences() { return true; } | |||||
| internal override void UnloadReferences() | |||||
| { | |||||
| //Don't need to clean _users - they're considered owned by server | |||||
| } | |||||
| internal void Update(APIUser model) | |||||
| { | |||||
| if (model.Email != null) | |||||
| Email = model.Email; | |||||
| if (model.IsVerified != null) | |||||
| IsVerified = model.IsVerified; | |||||
| } | |||||
| internal void AddUser(User user) => _users.TryAdd(user.Server?.Id ?? 0, user); | |||||
| internal void RemoveUser(User user) | |||||
| { | |||||
| if (_users.TryRemove(user.Server?.Id ?? 0, out user)) | |||||
| CheckUser(); | |||||
| } | |||||
| internal void CheckUser() | |||||
| { | |||||
| if (_users.Count == 0 && PrivateChannel == null) | |||||
| _client.GlobalUsers.TryRemove(Id); | |||||
| } | |||||
| public override bool Equals(object obj) => obj is GlobalUser && (obj as GlobalUser).Id == Id; | |||||
| public override int GetHashCode() => unchecked(Id.GetHashCode() + 7891); | |||||
| public override string ToString() => IdConvert.ToString(Id); | |||||
| }*/ | |||||
| } | |||||
| @@ -201,6 +201,10 @@ namespace Discord | |||||
| internal Message(ulong id, Channel channel, ulong userId) | internal Message(ulong id, Channel channel, ulong userId) | ||||
| { | { | ||||
| Id = id; | |||||
| Channel = channel; | |||||
| _userId = userId; | |||||
| Attachments = _initialAttachments; | Attachments = _initialAttachments; | ||||
| Embeds = _initialEmbeds; | Embeds = _initialEmbeds; | ||||
| } | } | ||||
| @@ -11,20 +11,21 @@ namespace Discord | |||||
| internal DiscordClient Client { get; } | internal DiscordClient Client { get; } | ||||
| /// <summary> Gets the unique identifier for this user. </summary> | /// <summary> Gets the unique identifier for this user. </summary> | ||||
| public ulong Id { get; private set; } | |||||
| public ulong Id { get; } | |||||
| /// <summary> Gets the email for this user. </summary> | /// <summary> Gets the email for this user. </summary> | ||||
| public string Email { get; private set; } | public string Email { get; private set; } | ||||
| /// <summary> Gets if the email for this user has been verified. </summary> | /// <summary> Gets if the email for this user has been verified. </summary> | ||||
| public bool? IsVerified { get; private set; } | public bool? IsVerified { get; private set; } | ||||
| internal Profile(DiscordClient client) | |||||
| internal Profile(DiscordClient client, ulong id) | |||||
| { | { | ||||
| Client = client; | Client = client; | ||||
| Id = id; | |||||
| } | } | ||||
| internal void Update(APIUser model) | internal void Update(APIUser model) | ||||
| { | { | ||||
| Id = model.Id; | |||||
| Email = model.Email; | Email = model.Email; | ||||
| IsVerified = model.IsVerified; | IsVerified = model.IsVerified; | ||||
| } | } | ||||
| @@ -52,6 +52,7 @@ namespace Discord | |||||
| { | { | ||||
| Id = id; | Id = id; | ||||
| Server = server; | Server = server; | ||||
| Permissions = new ServerPermissions(0); | Permissions = new ServerPermissions(0); | ||||
| Permissions.Lock(); | Permissions.Lock(); | ||||
| Color = new Color(0); | Color = new Color(0); | ||||
| @@ -78,6 +78,7 @@ namespace Discord | |||||
| { | { | ||||
| Client = client; | Client = client; | ||||
| Id = id; | Id = id; | ||||
| _channels = new ConcurrentDictionary<ulong, Channel>(); | _channels = new ConcurrentDictionary<ulong, Channel>(); | ||||
| _roles = new ConcurrentDictionary<ulong, Role>(); | _roles = new ConcurrentDictionary<ulong, Role>(); | ||||
| _users = new ConcurrentDictionary<ulong, Member>(); | _users = new ConcurrentDictionary<ulong, Member>(); | ||||
| @@ -97,7 +98,7 @@ namespace Discord | |||||
| if (model.AFKTimeout != null) | if (model.AFKTimeout != null) | ||||
| AFKTimeout = model.AFKTimeout.Value; | AFKTimeout = model.AFKTimeout.Value; | ||||
| _afkChannelId = model.AFKChannelId.Value; //Can be null | |||||
| _afkChannelId = model.AFKChannelId; //Can be null | |||||
| if (model.JoinedAt != null) | if (model.JoinedAt != null) | ||||
| JoinedAt = model.JoinedAt.Value; | JoinedAt = model.JoinedAt.Value; | ||||
| if (model.OwnerId != null) | if (model.OwnerId != null) | ||||
| @@ -196,7 +197,11 @@ namespace Discord | |||||
| #region Channels | #region Channels | ||||
| internal Channel AddChannel(ulong id) | internal Channel AddChannel(ulong id) | ||||
| => _channels.GetOrAdd(id, x => new Channel(Client, x, this)); | |||||
| { | |||||
| var channel = _channels.GetOrAdd(id, x => new Channel(Client, x, this)); | |||||
| Client.AddChannel(channel); | |||||
| return channel; | |||||
| } | |||||
| internal Channel RemoveChannel(ulong id) | internal Channel RemoveChannel(ulong id) | ||||
| { | { | ||||
| Channel channel; | Channel channel; | ||||
| @@ -136,9 +136,10 @@ namespace Discord | |||||
| internal User(DiscordClient client, ulong id, Server server) | internal User(DiscordClient client, ulong id, Server server) | ||||
| { | { | ||||
| Client = client; | Client = client; | ||||
| Id = id; | |||||
| Server = server; | Server = server; | ||||
| _roles = new Dictionary<ulong, Role>(); | |||||
| _roles = new Dictionary<ulong, Role>(); | |||||
| Status = UserStatus.Offline; | Status = UserStatus.Offline; | ||||
| if (server == null) | if (server == null) | ||||