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