diff --git a/src/Discord.Net/Collections/Members.cs b/src/Discord.Net/Collections/Members.cs index da9d81c0d..2ccc502f7 100644 --- a/src/Discord.Net/Collections/Members.cs +++ b/src/Discord.Net/Collections/Members.cs @@ -17,12 +17,18 @@ namespace Discord.Collections protected override void OnCreated(Member item) { item.Server.AddMember(item.UserId); + item.User.AddServer(item.ServerId); item.User.AddRef(); + if (item.UserId == _client.CurrentUserId) + item.Server.CurrentMember = item; } protected override void OnRemoved(Member item) { item.Server.RemoveMember(item.UserId); + item.User.RemoveServer(item.ServerId); item.User.RemoveRef(); + if (item.UserId == _client.CurrentUserId) + item.Server.CurrentMember = null; } internal Member this[string userId, string serverId] @@ -63,5 +69,20 @@ namespace Discord.Collections }); } } + + internal Member Find(string username, string discriminator) + { + if (username == null) throw new ArgumentNullException(nameof(username)); + if (discriminator == null) throw new ArgumentNullException(nameof(discriminator)); + + if (username.StartsWith("@")) + username = username.Substring(1); + + return this.Where(x => + string.Equals(x.Name, username, StringComparison.OrdinalIgnoreCase) && + x.Discriminator == discriminator + ) + .FirstOrDefault(); + } } } diff --git a/src/Discord.Net/Collections/Users.cs b/src/Discord.Net/Collections/Users.cs index 14da56c6b..56ffb060c 100644 --- a/src/Discord.Net/Collections/Users.cs +++ b/src/Discord.Net/Collections/Users.cs @@ -15,26 +15,9 @@ namespace Discord.Collections protected override void OnCreated(User item) { } protected override void OnRemoved(User item) { } - public User this[string id] => Get(id); - public User this[string name, string discriminator] - { - get - { - if (name == null) throw new ArgumentNullException(nameof(name)); - if (discriminator == null) throw new ArgumentNullException(nameof(discriminator)); - - if (name.StartsWith("@")) - name = name.Substring(1); - - return this.Where(x => - string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && - x.Discriminator == discriminator - ) - .FirstOrDefault(); - } - } + internal User this[string id] => Get(id); - public IEnumerable Find(string name) + internal IEnumerable Find(string name) { if (name == null) throw new ArgumentNullException(nameof(name)); diff --git a/src/Discord.Net/DiscordClient.API.cs b/src/Discord.Net/DiscordClient.API.cs index 0791d4b32..9a1c76918 100644 --- a/src/Discord.Net/DiscordClient.API.cs +++ b/src/Discord.Net/DiscordClient.API.cs @@ -241,14 +241,14 @@ namespace Discord => SendMessage(channelId, text, new string[0]); /// Sends a message to the provided channel, mentioning certain users. /// While not required, it is recommended to include a mention reference in the text (see User.Mention). - public Task SendMessage(Channel channel, string text, string[] mentions) - => SendMessage(channel?.Id, text, mentions); + public Task SendMessage(string channelId, string text, string[] mentions) + => SendMessage(_channels[channelId], text, mentions); /// Sends a message to the provided channel, mentioning certain users. /// While not required, it is recommended to include a mention reference in the text (see User.Mention). - public async Task SendMessage(string channelId, string text, string[] mentions, bool isTextToSpeech = false) + public async Task SendMessage(Channel channel, string text, string[] mentions, bool isTextToSpeech = false) { CheckReady(); - if (channelId == null) throw new ArgumentNullException(nameof(channelId)); + if (channel == null) throw new ArgumentNullException(nameof(channel)); if (text == null) throw new ArgumentNullException(nameof(text)); if (mentions == null) throw new ArgumentNullException(nameof(mentions)); @@ -261,13 +261,14 @@ namespace Discord var nonce = GenerateNonce(); if (_config.UseMessageQueue) { - var msg = _messages.GetOrAdd("nonce_" + nonce, channelId, _currentUserId); + var msg = _messages.GetOrAdd("nonce_" + nonce, channel.Id, _currentUserId); + var currentMember = _members[msg.UserId, channel.ServerId]; msg.Update(new Net.API.Message { Content = blockText, Timestamp = DateTime.UtcNow, - Author = new UserReference { Avatar = _currentUser.AvatarId, Discriminator = _currentUser.Discriminator, Id = _currentUser.Id, Username = _currentUser.Name }, - ChannelId = channelId, + Author = new UserReference { Avatar = currentMember.AvatarId, Discriminator = currentMember.Discriminator, Id = _currentUserId, Username = currentMember.Name }, + ChannelId = channel.Id, IsTextToSpeech = isTextToSpeech }); msg.IsQueued = true; @@ -277,8 +278,8 @@ namespace Discord } else { - var model = await _api.SendMessage(channelId, blockText, mentions, nonce, isTextToSpeech).ConfigureAwait(false); - var msg = _messages.GetOrAdd(model.Id, channelId, model.Author.Id); + var model = await _api.SendMessage(channel.Id, blockText, mentions, nonce, isTextToSpeech).ConfigureAwait(false); + var msg = _messages.GetOrAdd(model.Id, channel.Id, model.Author.Id); msg.Update(model); RaiseMessageSent(msg); } @@ -718,7 +719,8 @@ namespace Discord { CheckReady(); var response = await _api.ChangeUsername(newName, currentEmail, currentPassword).ConfigureAwait(false); - _currentUser.Update(response); + foreach (var membership in _currentUser.Memberships) + membership.Update(response); } /// Changes your email to newEmail. public async Task ChangeEmail(string newEmail, string currentPassword) @@ -731,8 +733,7 @@ namespace Discord public async Task ChangePassword(string newPassword, string currentEmail, string currentPassword) { CheckReady(); - var response = await _api.ChangePassword(newPassword, currentEmail, currentPassword).ConfigureAwait(false); - _currentUser.Update(response); + await _api.ChangePassword(newPassword, currentEmail, currentPassword).ConfigureAwait(false); } /// Changes your avatar. @@ -741,7 +742,8 @@ namespace Discord { CheckReady(); var response = await _api.ChangeAvatar(imageType, bytes, currentEmail, currentPassword).ConfigureAwait(false); - _currentUser.Update(response); + foreach (var membership in _currentUser.Memberships) + membership.Update(response); } } } diff --git a/src/Discord.Net/DiscordClient.Cache.cs b/src/Discord.Net/DiscordClient.Cache.cs index 3be2b2c49..1124f9efe 100644 --- a/src/Discord.Net/DiscordClient.Cache.cs +++ b/src/Discord.Net/DiscordClient.Cache.cs @@ -50,7 +50,7 @@ namespace Discord public User GetUser(string id) => _users[id]; /// Returns the user with the specified name and discriminator, or null if none was found. /// Name formats supported: Name and @Name. Search is case-insensitive. - public User GetUser(string name, string discriminator) => _users[name, discriminator]; + public User GetUser(string name, string discriminator) => _members[name, discriminator]?.User; /// Returns all users with the specified name across all servers. /// Name formats supported: Name and @Name. Search is case-insensitive. public IEnumerable FindUsers(string name) => _users.Find(name); diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 57a021b26..283f6d586 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -353,8 +353,6 @@ namespace Discord case "GUILD_MEMBER_ADD": { var data = e.Payload.ToObject(_serializer); - var user = _users.GetOrAdd(data.User.Id); - user.Update(data.User); var member = _members.GetOrAdd(data.User.Id, data.GuildId); member.Update(data); if (_config.TrackActivity) diff --git a/src/Discord.Net/Models/Member.cs b/src/Discord.Net/Models/Member.cs index b10fa15cb..bd8c7cb27 100644 --- a/src/Discord.Net/Models/Member.cs +++ b/src/Discord.Net/Models/Member.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using Discord.Net.API; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; @@ -9,6 +10,14 @@ namespace Discord { private readonly DiscordClient _client; + /// Returns the name of this user on this server. + public string Name { get; internal set; } + /// Returns the unique identifier for this user's current avatar. + public string AvatarId { get; internal set; } + /// Returns the URL to this user's current avatar. + public string AvatarUrl => Endpoints.UserAvatar(UserId, AvatarId); + /// Returns a by-name unique identifier separating this user from others with the same name. + public string Discriminator { get; internal set; } public DateTime JoinedAt { get; internal set; } public bool IsMuted { get; internal set; } @@ -62,19 +71,30 @@ namespace Discord public override string ToString() => UserId; - internal void Update(Net.API.MemberInfo model) + internal void Update(UserReference model) { + if (model.Avatar != null) + AvatarId = model.Avatar; + if (model.Discriminator != null) + Discriminator = model.Discriminator; + if (model.Username != null) + Name = model.Username; + } + internal void Update(MemberInfo model) + { + if (model.User != null) + Update(model.User); RoleIds = model.Roles; if (model.JoinedAt.HasValue) JoinedAt = model.JoinedAt.Value; } - internal void Update(Net.API.ExtendedMemberInfo model) + internal void Update(ExtendedMemberInfo model) { - Update(model as Net.API.MemberInfo); + Update(model as MemberInfo); IsDeafened = model.IsDeafened; IsMuted = model.IsMuted; } - internal void Update(Net.API.PresenceMemberInfo model) + internal void Update(PresenceMemberInfo model) { if (Status != model.Status) { @@ -86,7 +106,7 @@ namespace Discord } GameId = model.GameId; } - internal void Update(Net.API.VoiceMemberInfo model) + internal void Update(VoiceMemberInfo model) { IsDeafened = model.IsDeafened; IsMuted = model.IsMuted; diff --git a/src/Discord.Net/Models/Server.cs b/src/Discord.Net/Models/Server.cs index 75785c7a5..c54e1f689 100644 --- a/src/Discord.Net/Models/Server.cs +++ b/src/Discord.Net/Models/Server.cs @@ -16,6 +16,8 @@ namespace Discord public string Id { get; } /// Returns the name of this channel. public string Name { get; internal set; } + /// Returns the current logged-in user's data for this server. + public Member CurrentMember { get; internal set; } /// Returns the amount of time (in seconds) a user must be inactive for until they are automatically moved to the AFK channel (see AFKChannel). public int AFKTimeout { get; internal set; } @@ -130,9 +132,8 @@ namespace Discord var members = _client.Members; foreach (var subModel in model.Members) { - var user = users.GetOrAdd(subModel.User.Id); + users.GetOrAdd(subModel.User.Id); var member = members.GetOrAdd(subModel.User.Id, Id); - user.Update(subModel.User); member.Update(subModel); } foreach (var subModel in model.VoiceStates) diff --git a/src/Discord.Net/Models/User.cs b/src/Discord.Net/Models/User.cs index e117cec8d..d757621a9 100644 --- a/src/Discord.Net/Models/User.cs +++ b/src/Discord.Net/Models/User.cs @@ -1,6 +1,7 @@ using Discord.Net.API; using Newtonsoft.Json; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -12,18 +13,13 @@ namespace Discord private readonly DiscordClient _client; private int _refs; private DateTime? _lastPrivateActivity; + private ConcurrentDictionary _servers; /// Returns the unique identifier for this user. public string Id { get; } - /// Returns the name of this channel. - public string Name { get; internal set; } - - /// Returns the unique identifier for this user's current avatar. - public string AvatarId { get; internal set; } - /// Returns the URL to this user's current avatar. - public string AvatarUrl => Endpoints.UserAvatar(Id, AvatarId); - /// Returns a by-name unique identifier separating this user from others with the same name. - public string Discriminator { get; internal set; } + /// Returns the name of this user. + public string Name => Memberships.Where(x => x.GameId != null).Select(x => x.GameId).FirstOrDefault(); + /// Returns the email for this user. /// This field is only ever populated for the current logged in user. [JsonIgnore] @@ -40,9 +36,11 @@ namespace Discord public Channel PrivateChannel => _client.Channels[PrivateChannelId]; /// Returns a collection of all server-specific data for every server this user is a member of. - public IEnumerable Memberships => _client.Servers.Where(x => x.HasMember(Id)).Select(x => _client.Members[Id, x?.Id]); + public IEnumerable Memberships => _servers.Select(x => _client.GetMember(x.Key, Id)); /// Returns a collection of all servers this user is a member of. - public IEnumerable Servers => _client.Servers.Where(x => x.HasMember(Id)); + public IEnumerable Servers => _servers.Select(x => _client.GetServer(x.Key)); + /// Returns a collection of the ids of all servers this user is a member of. + public IEnumerable ServersIds => _servers.Select(x => x.Key); /// Returns a collection of all messages this user has sent that are still in cache. public IEnumerable Messages => _client.Messages.Where(x => x.UserId == Id); @@ -71,17 +69,11 @@ namespace Discord { _client = client; Id = id; - } + _servers = new ConcurrentDictionary(); + } - internal void Update(UserReference model) - { - AvatarId = model.Avatar; - Discriminator = model.Discriminator; - Name = model.Username; - } internal void Update(SelfUserInfo model) { - Update(model as UserReference); Email = model.Email; IsVerified = model.IsVerified; } @@ -92,7 +84,17 @@ namespace Discord } public override string ToString() => Name; - + + internal void AddServer(string serverId) + { + _servers.TryAdd(serverId, true); + } + internal bool RemoveServer(string serverId) + { + bool ignored; + return _servers.TryRemove(serverId, out ignored); + } + public void AddRef() { Interlocked.Increment(ref _refs);