| @@ -148,27 +148,6 @@ | |||||
| <Compile Include="..\Discord.Net\Audio\VoiceBuffer.cs"> | <Compile Include="..\Discord.Net\Audio\VoiceBuffer.cs"> | ||||
| <Link>Audio\VoiceBuffer.cs</Link> | <Link>Audio\VoiceBuffer.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\Collections\AsyncCollection.cs"> | |||||
| <Link>Collections\AsyncCollection.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\Collections\Channels.cs"> | |||||
| <Link>Collections\Channels.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\Collections\Members.cs"> | |||||
| <Link>Collections\Members.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\Collections\Messages.cs"> | |||||
| <Link>Collections\Messages.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\Collections\Roles.cs"> | |||||
| <Link>Collections\Roles.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\Collections\Servers.cs"> | |||||
| <Link>Collections\Servers.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\Collections\Users.cs"> | |||||
| <Link>Collections\Users.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\DiscordAPIClient.cs"> | <Compile Include="..\Discord.Net\DiscordAPIClient.cs"> | ||||
| <Link>DiscordAPIClient.cs</Link> | <Link>DiscordAPIClient.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| @@ -250,6 +229,9 @@ | |||||
| <Compile Include="..\Discord.Net\HttpException.cs"> | <Compile Include="..\Discord.Net\HttpException.cs"> | ||||
| <Link>HttpException.cs</Link> | <Link>HttpException.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\Models\AsyncCollection.cs"> | |||||
| <Link>Models\AsyncCollection.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\Models\Channel.cs"> | <Compile Include="..\Discord.Net\Models\Channel.cs"> | ||||
| <Link>Models\Channel.cs</Link> | <Link>Models\Channel.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| @@ -1,47 +0,0 @@ | |||||
| using System; | |||||
| namespace Discord.Collections | |||||
| { | |||||
| internal sealed class Channels : AsyncCollection<Channel> | |||||
| { | |||||
| public Channels(DiscordClient client, object writerLock) | |||||
| : base(client, writerLock) { } | |||||
| public Channel GetOrAdd(string id, string serverId, string recipientId = null) | |||||
| => GetOrAdd(id, () => new Channel(_client, id, serverId, recipientId)); | |||||
| protected override void OnCreated(Channel item) | |||||
| { | |||||
| if (!item.IsPrivate) | |||||
| item.Server.AddChannel(item.Id); | |||||
| if (item.RecipientId != null) | |||||
| { | |||||
| var user = item.Recipient; | |||||
| if (user.PrivateChannelId != null) | |||||
| throw new Exception("User already has a private channel."); | |||||
| user.PrivateChannelId = item.Id; | |||||
| user.AddRef(); | |||||
| } | |||||
| } | |||||
| protected override void OnRemoved(Channel item) | |||||
| { | |||||
| if (!item.IsPrivate) | |||||
| { | |||||
| var server = item.Server; | |||||
| if (server != null) | |||||
| item.Server.RemoveChannel(item.Id); | |||||
| } | |||||
| if (item.RecipientId != null) | |||||
| { | |||||
| var user = item.Recipient; | |||||
| if (user != null) | |||||
| { | |||||
| if (user.PrivateChannelId != item.Id) | |||||
| throw new Exception("User has a different private channel."); | |||||
| user.PrivateChannelId = null; | |||||
| user.RemoveRef(); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,42 +0,0 @@ | |||||
| namespace Discord.Collections | |||||
| { | |||||
| internal sealed class Members : AsyncCollection<Member> | |||||
| { | |||||
| public Members(DiscordClient client, object writerLock) | |||||
| : base(client, writerLock) { } | |||||
| private string GetKey(string userId, string serverId) | |||||
| => serverId + '_' + userId; | |||||
| public Member this[string userId, string serverId] | |||||
| => this[GetKey(userId, serverId)]; | |||||
| public Member GetOrAdd(string userId, string serverId) | |||||
| => GetOrAdd(GetKey(userId, serverId), () => new Member(_client, userId, serverId)); | |||||
| public Member TryRemove(string userId, string serverId) | |||||
| => TryRemove(GetKey(userId, serverId)); | |||||
| protected override void OnCreated(Member item) | |||||
| { | |||||
| item.Server.AddMember(item); | |||||
| item.User.AddServer(item.ServerId); | |||||
| item.User.AddRef(); | |||||
| if (item.UserId == _client.CurrentUserId) | |||||
| item.Server.CurrentMember = item; | |||||
| } | |||||
| protected override void OnRemoved(Member item) | |||||
| { | |||||
| var server = item.Server; | |||||
| if (server != null) | |||||
| { | |||||
| server.RemoveMember(item); | |||||
| if (item.UserId == _client.CurrentUserId) | |||||
| server.CurrentMember = null; | |||||
| } | |||||
| var user = item.User; | |||||
| if (user != null) | |||||
| { | |||||
| user.RemoveServer(item.ServerId); | |||||
| user.RemoveRef(); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,26 +0,0 @@ | |||||
| namespace Discord.Collections | |||||
| { | |||||
| internal sealed class Messages : AsyncCollection<Message> | |||||
| { | |||||
| public Messages(DiscordClient client, object writerLock) | |||||
| : base(client, writerLock) { } | |||||
| public Message GetOrAdd(string id, string channelId, string userId) | |||||
| => GetOrAdd(id, () => new Message(_client, id, channelId, userId)); | |||||
| protected override void OnCreated(Message item) | |||||
| { | |||||
| item.Channel.AddMessage(item.Id); | |||||
| item.User.AddRef(); | |||||
| } | |||||
| protected override void OnRemoved(Message item) | |||||
| { | |||||
| var channel = item.Channel; | |||||
| if (channel != null) | |||||
| channel.RemoveMessage(item.Id); | |||||
| var user = item.User; | |||||
| if (user != null) | |||||
| user.RemoveRef(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,22 +0,0 @@ | |||||
| namespace Discord.Collections | |||||
| { | |||||
| internal sealed class Roles : AsyncCollection<Role> | |||||
| { | |||||
| public Roles(DiscordClient client, object writerLock) | |||||
| : base(client, writerLock) { } | |||||
| public Role GetOrAdd(string id, string serverId) | |||||
| => GetOrAdd(id, () => new Role(_client, id, serverId)); | |||||
| protected override void OnCreated(Role item) | |||||
| { | |||||
| item.Server.AddRole(item.Id); | |||||
| } | |||||
| protected override void OnRemoved(Role item) | |||||
| { | |||||
| var server = item.Server; | |||||
| if (server != null) | |||||
| item.Server.RemoveRole(item.Id); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,30 +0,0 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| namespace Discord.Collections | |||||
| { | |||||
| internal sealed class Servers : AsyncCollection<Server> | |||||
| { | |||||
| public Servers(DiscordClient client, object writerLock) | |||||
| : base(client, writerLock) { } | |||||
| public Server GetOrAdd(string id) | |||||
| => base.GetOrAdd(id, () => new Server(_client, id)); | |||||
| protected override void OnRemoved(Server item) | |||||
| { | |||||
| var channels = _client.Channels; | |||||
| foreach (var channelId in item.ChannelIds) | |||||
| channels.TryRemove(channelId); | |||||
| var members = _client.Members; | |||||
| foreach (var userId in item.UserIds) | |||||
| members.TryRemove(userId, item.Id); | |||||
| var roles = _client.Roles; | |||||
| foreach (var roleId in item.RoleIds) | |||||
| roles.TryRemove(roleId); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,10 +0,0 @@ | |||||
| namespace Discord.Collections | |||||
| { | |||||
| internal sealed class Users : AsyncCollection<User> | |||||
| { | |||||
| public Users(DiscordClient client, object writerLock) | |||||
| : base(client, writerLock) { } | |||||
| public User GetOrAdd(string id) => GetOrAdd(id, () => new User(_client, id)); | |||||
| } | |||||
| } | |||||
| @@ -5,6 +5,21 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public class BanEventArgs : EventArgs | |||||
| { | |||||
| public User User { get; } | |||||
| public string UserId { get; } | |||||
| public Server Server { get; } | |||||
| public string ServerId => Server.Id; | |||||
| internal BanEventArgs(User user, string userId, Server server) | |||||
| { | |||||
| User = user; | |||||
| UserId = userId; | |||||
| Server = server; | |||||
| } | |||||
| } | |||||
| public partial class DiscordClient | public partial class DiscordClient | ||||
| { | { | ||||
| public event EventHandler<BanEventArgs> BanAdded; | public event EventHandler<BanEventArgs> BanAdded; | ||||
| @@ -1,4 +1,3 @@ | |||||
| using Discord.Collections; | |||||
| using Discord.Net; | using Discord.Net; | ||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| @@ -8,7 +7,16 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public sealed class ChannelEventArgs : EventArgs | |||||
| internal sealed class Channels : AsyncCollection<Channel> | |||||
| { | |||||
| public Channels(DiscordClient client, object writerLock) | |||||
| : base(client, writerLock, x => x.OnCached(), x => x.OnUncached()) { } | |||||
| public Channel GetOrAdd(string id, string serverId, string recipientId = null) | |||||
| => GetOrAdd(id, () => new Channel(_client, id, serverId, recipientId)); | |||||
| } | |||||
| public class ChannelEventArgs : EventArgs | |||||
| { | { | ||||
| public Channel Channel { get; } | public Channel Channel { get; } | ||||
| public string ChannelId => Channel.Id; | public string ChannelId => Channel.Id; | ||||
| @@ -1,4 +1,3 @@ | |||||
| using Discord.Collections; | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Linq; | using System.Linq; | ||||
| @@ -6,54 +5,66 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public sealed class MemberTypingEventArgs : EventArgs | |||||
| internal sealed class Members : AsyncCollection<Member> | |||||
| { | |||||
| public Members(DiscordClient client, object writerLock) | |||||
| : base(client, writerLock, x => x.OnCached(), x => x.OnUncached()) { } | |||||
| private string GetKey(string userId, string serverId) | |||||
| => serverId + '_' + userId; | |||||
| public Member this[string userId, string serverId] | |||||
| => this[GetKey(userId, serverId)]; | |||||
| public Member GetOrAdd(string userId, string serverId) | |||||
| => GetOrAdd(GetKey(userId, serverId), () => new Member(_client, userId, serverId)); | |||||
| public Member TryRemove(string userId, string serverId) | |||||
| => TryRemove(GetKey(userId, serverId)); | |||||
| } | |||||
| public class MemberEventArgs : EventArgs | |||||
| { | { | ||||
| public Channel Channel { get; } | |||||
| public string ChannelId => Channel.Id; | |||||
| public Server Server => Channel.Server; | |||||
| public string ServerId => Channel.ServerId; | |||||
| public Member Member { get; } | public Member Member { get; } | ||||
| public string UserId => User.Id; | |||||
| public User User => Member.User; | public User User => Member.User; | ||||
| public string UserId => Member.UserId; | |||||
| public Server Server => Member.Server; | |||||
| public string ServerId => Member.ServerId; | |||||
| internal MemberEventArgs(Member member) { Member = member; } | |||||
| } | |||||
| public class MemberChannelEventArgs : MemberEventArgs | |||||
| { | |||||
| public Channel Channel { get; } | |||||
| public string ChannelId => Channel.Id; | |||||
| internal MemberTypingEventArgs(Member member, Channel channel) | |||||
| internal MemberChannelEventArgs(Member member, Channel channel) | |||||
| : base(member) | |||||
| { | { | ||||
| Member = member; | |||||
| Channel = channel; | Channel = channel; | ||||
| } | } | ||||
| } | } | ||||
| public sealed class MemberIsSpeakingEventArgs : EventArgs | |||||
| public class MemberIsSpeakingEventArgs : MemberChannelEventArgs | |||||
| { | { | ||||
| public Channel Channel => Member.VoiceChannel; | |||||
| public string ChannelId => Member.VoiceChannelId; | |||||
| public Server Server => Member.Server; | |||||
| public string ServerId => Member.ServerId; | |||||
| public User User => Member.User; | |||||
| public string UserId => Member.UserId; | |||||
| public Member Member { get; } | |||||
| public bool IsSpeaking { get; } | public bool IsSpeaking { get; } | ||||
| internal MemberIsSpeakingEventArgs(Member member, bool isSpeaking) | |||||
| internal MemberIsSpeakingEventArgs(Member member, Channel channel, bool isSpeaking) | |||||
| : base(member, channel) | |||||
| { | { | ||||
| Member = member; | |||||
| IsSpeaking = isSpeaking; | IsSpeaking = isSpeaking; | ||||
| } | } | ||||
| } | } | ||||
| public partial class DiscordClient | public partial class DiscordClient | ||||
| { | { | ||||
| public event EventHandler<MemberTypingEventArgs> UserIsTyping; | |||||
| public event EventHandler<MemberChannelEventArgs> UserIsTyping; | |||||
| private void RaiseUserIsTyping(Member member, Channel channel) | private void RaiseUserIsTyping(Member member, Channel channel) | ||||
| { | { | ||||
| if (UserIsTyping != null) | if (UserIsTyping != null) | ||||
| RaiseEvent(nameof(UserIsTyping), () => UserIsTyping(this, new MemberTypingEventArgs(member, channel))); | |||||
| RaiseEvent(nameof(UserIsTyping), () => UserIsTyping(this, new MemberChannelEventArgs(member, channel))); | |||||
| } | } | ||||
| public event EventHandler<MemberIsSpeakingEventArgs> UserIsSpeaking; | public event EventHandler<MemberIsSpeakingEventArgs> UserIsSpeaking; | ||||
| private void RaiseUserIsSpeaking(Member member, bool isSpeaking) | |||||
| private void RaiseUserIsSpeaking(Member member, Channel channel, bool isSpeaking) | |||||
| { | { | ||||
| if (UserIsSpeaking != null) | if (UserIsSpeaking != null) | ||||
| RaiseEvent(nameof(UserIsSpeaking), () => UserIsSpeaking(this, new MemberIsSpeakingEventArgs(member, isSpeaking))); | |||||
| RaiseEvent(nameof(UserIsSpeaking), () => UserIsSpeaking(this, new MemberIsSpeakingEventArgs(member, channel, isSpeaking))); | |||||
| } | } | ||||
| internal Members Members => _members; | internal Members Members => _members; | ||||
| @@ -1,5 +1,4 @@ | |||||
| using Discord.API; | using Discord.API; | ||||
| using Discord.Collections; | |||||
| using Discord.Net; | using Discord.Net; | ||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| @@ -9,6 +8,30 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| internal sealed class Messages : AsyncCollection<Message> | |||||
| { | |||||
| public Messages(DiscordClient client, object writerLock) | |||||
| : base(client, writerLock, x => x.OnCached(), x => x.OnUncached()) { } | |||||
| public Message GetOrAdd(string id, string channelId, string userId) | |||||
| => GetOrAdd(id, () => new Message(_client, id, channelId, userId)); | |||||
| } | |||||
| public class MessageEventArgs : EventArgs | |||||
| { | |||||
| public Message Message { get; } | |||||
| public string MessageId => Message.Id; | |||||
| public Member Member => Message.Member; | |||||
| public Channel Channel => Message.Channel; | |||||
| public string ChannelId => Message.ChannelId; | |||||
| public Server Server => Message.Server; | |||||
| public string ServerId => Message.ServerId; | |||||
| public User User => Member.User; | |||||
| public string UserId => Message.UserId; | |||||
| internal MessageEventArgs(Message msg) { Message = msg; } | |||||
| } | |||||
| public partial class DiscordClient | public partial class DiscordClient | ||||
| { | { | ||||
| public const int MaxMessageSize = 2000; | public const int MaxMessageSize = 2000; | ||||
| @@ -1,4 +1,3 @@ | |||||
| using Discord.Collections; | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Linq; | using System.Linq; | ||||
| @@ -6,6 +5,25 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| internal sealed class Roles : AsyncCollection<Role> | |||||
| { | |||||
| public Roles(DiscordClient client, object writerLock) | |||||
| : base(client, writerLock, x => x.OnCached(), x => x.OnUncached()) { } | |||||
| public Role GetOrAdd(string id, string serverId) | |||||
| => GetOrAdd(id, () => new Role(_client, id, serverId)); | |||||
| } | |||||
| public class RoleEventArgs : EventArgs | |||||
| { | |||||
| public Role Role { get; } | |||||
| public string RoleId => Role.Id; | |||||
| public Server Server => Role.Server; | |||||
| public string ServerId => Role.ServerId; | |||||
| internal RoleEventArgs(Role role) { Role = role; } | |||||
| } | |||||
| public partial class DiscordClient | public partial class DiscordClient | ||||
| { | { | ||||
| public event EventHandler<RoleEventArgs> RoleCreated; | public event EventHandler<RoleEventArgs> RoleCreated; | ||||
| @@ -1,4 +1,3 @@ | |||||
| using Discord.Collections; | |||||
| using Discord.Net; | using Discord.Net; | ||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| @@ -8,7 +7,16 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public sealed class ServerEventArgs : EventArgs | |||||
| internal sealed class Servers : AsyncCollection<Server> | |||||
| { | |||||
| public Servers(DiscordClient client, object writerLock) | |||||
| : base(client, writerLock, x => x.OnCached(), x => x.OnUncached()) { } | |||||
| public Server GetOrAdd(string id) | |||||
| => base.GetOrAdd(id, () => new Server(_client, id)); | |||||
| } | |||||
| public class ServerEventArgs : EventArgs | |||||
| { | { | ||||
| public Server Server { get; } | public Server Server { get; } | ||||
| public string ServerId => Server.Id; | public string ServerId => Server.Id; | ||||
| @@ -1,5 +1,4 @@ | |||||
| using Discord.API; | using Discord.API; | ||||
| using Discord.Collections; | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Linq; | using System.Linq; | ||||
| @@ -7,6 +6,14 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| internal sealed class Users : AsyncCollection<User> | |||||
| { | |||||
| public Users(DiscordClient client, object writerLock) | |||||
| : base(client, writerLock, x => x.OnCached(), x => x.OnUncached()) { } | |||||
| public User GetOrAdd(string id) => GetOrAdd(id, () => new User(_client, id)); | |||||
| } | |||||
| public sealed class UserEventArgs : EventArgs | public sealed class UserEventArgs : EventArgs | ||||
| { | { | ||||
| public User User { get; } | public User User { get; } | ||||
| @@ -1,5 +1,6 @@ | |||||
| using Discord.Audio; | using Discord.Audio; | ||||
| using System; | using System; | ||||
| using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| @@ -1,5 +1,4 @@ | |||||
| using Discord.API; | using Discord.API; | ||||
| using Discord.Collections; | |||||
| using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System; | using System; | ||||
| @@ -10,54 +9,6 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public sealed class MessageEventArgs : EventArgs | |||||
| { | |||||
| public Message Message { get; } | |||||
| public string MessageId => Message.Id; | |||||
| public Member Member => Message.Member; | |||||
| public Channel Channel => Message.Channel; | |||||
| public string ChannelId => Message.ChannelId; | |||||
| public Server Server => Message.Server; | |||||
| public string ServerId => Message.ServerId; | |||||
| public User User => Member.User; | |||||
| public string UserId => Message.UserId; | |||||
| internal MessageEventArgs(Message msg) { Message = msg; } | |||||
| } | |||||
| public sealed class RoleEventArgs : EventArgs | |||||
| { | |||||
| public Role Role { get; } | |||||
| public string RoleId => Role.Id; | |||||
| public Server Server => Role.Server; | |||||
| public string ServerId => Role.ServerId; | |||||
| internal RoleEventArgs(Role role) { Role = role; } | |||||
| } | |||||
| public sealed class BanEventArgs : EventArgs | |||||
| { | |||||
| public User User { get; } | |||||
| public string UserId { get; } | |||||
| public Server Server { get; } | |||||
| public string ServerId => Server.Id; | |||||
| internal BanEventArgs(User user, string userId, Server server) | |||||
| { | |||||
| User = user; | |||||
| UserId = userId; | |||||
| Server = server; | |||||
| } | |||||
| } | |||||
| public sealed class MemberEventArgs : EventArgs | |||||
| { | |||||
| public Member Member { get; } | |||||
| public User User => Member.User; | |||||
| public string UserId => Member.UserId; | |||||
| public Server Server => Member.Server; | |||||
| public string ServerId => Member.ServerId; | |||||
| internal MemberEventArgs(Member member) { Member = member; } | |||||
| } | |||||
| /// <summary> Provides a connection to the DiscordApp service. </summary> | /// <summary> Provides a connection to the DiscordApp service. </summary> | ||||
| public partial class DiscordClient : DiscordWSClient | public partial class DiscordClient : DiscordWSClient | ||||
| { | { | ||||
| @@ -106,7 +57,7 @@ namespace Discord | |||||
| if (member.ServerId == e.ServerId && member.IsSpeaking) | if (member.ServerId == e.ServerId && member.IsSpeaking) | ||||
| { | { | ||||
| member.IsSpeaking = false; | member.IsSpeaking = false; | ||||
| RaiseUserIsSpeaking(member, false); | |||||
| RaiseUserIsSpeaking(member, _channels[_voiceSocket.CurrentChannelId], false); | |||||
| } | } | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -157,10 +108,10 @@ namespace Discord | |||||
| $"Deleted Role: {e.Server?.Name ?? "[Private]"}/{e.Role.Name}" + | $"Deleted Role: {e.Server?.Name ?? "[Private]"}/{e.Role.Name}" + | ||||
| (showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.RoleId})." : "")); | (showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.RoleId})." : "")); | ||||
| BanAdded += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | BanAdded += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | ||||
| $"Added Ban: {e.Server?.Name ?? "[Private]"}/{e.User?.Name ?? "Unknown"}" + | |||||
| $"Added Ban: {e.Server?.Name ?? "[Private]"}/{e.User?.Name ?? e.UserId}" + | |||||
| (showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.UserId})." : "")); | (showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.UserId})." : "")); | ||||
| BanRemoved += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | BanRemoved += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | ||||
| $"Removed Ban: {e.Server?.Name ?? "[Private]"}/{e.User?.Name ?? "Unknown"}" + | |||||
| $"Removed Ban: {e.Server?.Name ?? "[Private]"}/{e.User?.Name ?? e.UserId}" + | |||||
| (showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.UserId})." : "")); | (showIDs ? $" ({e.ServerId ?? "[Private]"}/{e.UserId})." : "")); | ||||
| UserAdded += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | UserAdded += (s, e) => RaiseOnLog(LogMessageSeverity.Info, LogMessageSource.Client, | ||||
| $"Added Member: {e.Server?.Name ?? "[Private]"}/{e.User.Name}" + | $"Added Member: {e.Server?.Name ?? "[Private]"}/{e.User.Name}" + | ||||
| @@ -246,7 +197,8 @@ namespace Discord | |||||
| if (member.IsSpeaking != value) | if (member.IsSpeaking != value) | ||||
| { | { | ||||
| member.IsSpeaking = value; | member.IsSpeaking = value; | ||||
| RaiseUserIsSpeaking(member, value); | |||||
| var channel = _channels[_voiceSocket.CurrentChannelId]; | |||||
| RaiseUserIsSpeaking(member, channel, value); | |||||
| if (Config.TrackActivity) | if (Config.TrackActivity) | ||||
| member.UpdateActivity(); | member.UpdateActivity(); | ||||
| } | } | ||||
| @@ -665,7 +617,7 @@ namespace Discord | |||||
| if (data.ChannelId != member.VoiceChannelId && member.IsSpeaking) | if (data.ChannelId != member.VoiceChannelId && member.IsSpeaking) | ||||
| { | { | ||||
| member.IsSpeaking = false; | member.IsSpeaking = false; | ||||
| RaiseUserIsSpeaking(member, false); | |||||
| RaiseUserIsSpeaking(member, _channels[member.VoiceChannelId], false); | |||||
| } | } | ||||
| member.Update(data); | member.Update(data); | ||||
| RaiseUserVoiceStateUpdated(member); | RaiseUserVoiceStateUpdated(member); | ||||
| @@ -4,7 +4,7 @@ using System.Collections.Concurrent; | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Linq; | using System.Linq; | ||||
| namespace Discord.Collections | |||||
| namespace Discord | |||||
| { | { | ||||
| internal abstract class AsyncCollection<TValue> : IEnumerable<TValue> | internal abstract class AsyncCollection<TValue> : IEnumerable<TValue> | ||||
| where TValue : class | where TValue : class | ||||
| @@ -52,13 +52,16 @@ namespace Discord.Collections | |||||
| protected readonly DiscordClient _client; | protected readonly DiscordClient _client; | ||||
| protected readonly ConcurrentDictionary<string, TValue> _dictionary; | protected readonly ConcurrentDictionary<string, TValue> _dictionary; | ||||
| private readonly Action<TValue> _onCache, _onUncache; | |||||
| protected AsyncCollection(DiscordClient client, object writerLock) | |||||
| protected AsyncCollection(DiscordClient client, object writerLock, Action<TValue> onCache, Action<TValue> onUncache) | |||||
| { | { | ||||
| _client = client; | _client = client; | ||||
| _writerLock = writerLock; | _writerLock = writerLock; | ||||
| _dictionary = new ConcurrentDictionary<string, TValue>(); | _dictionary = new ConcurrentDictionary<string, TValue>(); | ||||
| } | |||||
| _onCache = onCache; | |||||
| _onUncache = onUncache; | |||||
| } | |||||
| public TValue this[string key] | public TValue this[string key] | ||||
| { | { | ||||
| @@ -85,7 +88,7 @@ namespace Discord.Collections | |||||
| result = _dictionary.GetOrAdd(key, newItem); | result = _dictionary.GetOrAdd(key, newItem); | ||||
| if (result == newItem) | if (result == newItem) | ||||
| { | { | ||||
| OnCreated(newItem); | |||||
| _onCache(result); | |||||
| RaiseItemCreated(result); | RaiseItemCreated(result); | ||||
| } | } | ||||
| } | } | ||||
| @@ -100,7 +103,7 @@ namespace Discord.Collections | |||||
| TValue result; | TValue result; | ||||
| if (_dictionary.TryRemove(key, out result)) | if (_dictionary.TryRemove(key, out result)) | ||||
| { | { | ||||
| OnRemoved(result); //TODO: If this object is accessed before OnRemoved finished firing, properties such as Server.Channels will have null elements | |||||
| _onUncache(result); //TODO: If this object is accessed before OnRemoved finished firing, properties such as Server.Channels will have null elements | |||||
| return result; | return result; | ||||
| } | } | ||||
| } | } | ||||
| @@ -130,16 +133,7 @@ namespace Discord.Collections | |||||
| } | } | ||||
| } | } | ||||
| protected virtual void OnCreated(TValue item) { } | |||||
| protected virtual void OnRemoved(TValue item) { } | |||||
| public IEnumerator<TValue> GetEnumerator() | |||||
| { | |||||
| return _dictionary.Select(x => x.Value).GetEnumerator(); | |||||
| } | |||||
| IEnumerator IEnumerable.GetEnumerator() | |||||
| { | |||||
| return GetEnumerator(); | |||||
| } | |||||
| public IEnumerator<TValue> GetEnumerator() => _dictionary.Select(x => x.Value).GetEnumerator(); | |||||
| IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,5 +1,6 @@ | |||||
| using Discord.API; | using Discord.API; | ||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System; | |||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Linq; | using System.Linq; | ||||
| @@ -29,6 +30,7 @@ namespace Discord | |||||
| private readonly DiscordClient _client; | private readonly DiscordClient _client; | ||||
| private readonly ConcurrentDictionary<string, bool> _messages; | private readonly ConcurrentDictionary<string, bool> _messages; | ||||
| private bool _areMembersStale; | private bool _areMembersStale; | ||||
| private bool _hasRef; | |||||
| /// <summary> Returns the unique identifier for this channel. </summary> | /// <summary> Returns the unique identifier for this channel. </summary> | ||||
| public string Id { get; } | public string Id { get; } | ||||
| @@ -101,6 +103,39 @@ namespace Discord | |||||
| _permissionOverwrites = _initialPermissionsOverwrites; | _permissionOverwrites = _initialPermissionsOverwrites; | ||||
| _areMembersStale = true; | _areMembersStale = true; | ||||
| } | } | ||||
| internal void OnCached() | |||||
| { | |||||
| var server = Server; | |||||
| if (server != null) | |||||
| server.AddChannel(Id); | |||||
| if (RecipientId != null) | |||||
| { | |||||
| var user = Recipient; | |||||
| if (user != null) | |||||
| { | |||||
| user.PrivateChannelId = Id; | |||||
| user.AddRef(); | |||||
| _hasRef = true; | |||||
| } | |||||
| } | |||||
| } | |||||
| internal void OnUncached() | |||||
| { | |||||
| var server = Server; | |||||
| if (server != null) | |||||
| server.RemoveChannel(Id); | |||||
| if (RecipientId != null) | |||||
| { | |||||
| var user = Recipient; | |||||
| if (user != null) | |||||
| { | |||||
| user.PrivateChannelId = null; | |||||
| if (_hasRef) | |||||
| user.RemoveRef(); | |||||
| } | |||||
| } | |||||
| _hasRef = false; | |||||
| } | |||||
| internal void Update(ChannelReference model) | internal void Update(ChannelReference model) | ||||
| { | { | ||||
| @@ -11,6 +11,7 @@ namespace Discord | |||||
| { | { | ||||
| private readonly DiscordClient _client; | private readonly DiscordClient _client; | ||||
| private ConcurrentDictionary<string, PackedChannelPermissions> _permissions; | private ConcurrentDictionary<string, PackedChannelPermissions> _permissions; | ||||
| private bool _hasRef; | |||||
| /// <summary> Returns the name of this user on this server. </summary> | /// <summary> Returns the name of this user on this server. </summary> | ||||
| public string Name { get; private set; } | public string Name { get; private set; } | ||||
| @@ -77,6 +78,41 @@ namespace Discord | |||||
| RoleIds = _initialRoleIds; | RoleIds = _initialRoleIds; | ||||
| _permissions = new ConcurrentDictionary<string, PackedChannelPermissions>(); | _permissions = new ConcurrentDictionary<string, PackedChannelPermissions>(); | ||||
| } | } | ||||
| internal void OnCached() | |||||
| { | |||||
| var server = Server; | |||||
| if (server != null) | |||||
| { | |||||
| server.AddMember(this); | |||||
| if (UserId == _client.CurrentUserId) | |||||
| server.CurrentMember = this; | |||||
| } | |||||
| var user = User; | |||||
| if (user != null) | |||||
| { | |||||
| user.AddServer(ServerId); | |||||
| user.AddRef(); | |||||
| _hasRef = true; | |||||
| } | |||||
| } | |||||
| internal void OnUncached() | |||||
| { | |||||
| var server = Server; | |||||
| if (server != null) | |||||
| { | |||||
| server.RemoveMember(this); | |||||
| if (UserId == _client.CurrentUserId) | |||||
| server.CurrentMember = null; | |||||
| } | |||||
| var user = User; | |||||
| if (user != null) | |||||
| { | |||||
| user.RemoveServer(ServerId); | |||||
| if (_hasRef) | |||||
| user.RemoveRef(); | |||||
| } | |||||
| _hasRef = false; | |||||
| } | |||||
| public override string ToString() => UserId; | public override string ToString() => UserId; | ||||
| @@ -93,6 +93,7 @@ namespace Discord | |||||
| private readonly DiscordClient _client; | private readonly DiscordClient _client; | ||||
| private string _cleanText; | private string _cleanText; | ||||
| private bool _gotRef; | |||||
| /// <summary> Returns the global unique identifier for this message. </summary> | /// <summary> Returns the global unique identifier for this message. </summary> | ||||
| public string Id { get; internal set; } | public string Id { get; internal set; } | ||||
| @@ -154,16 +155,7 @@ namespace Discord | |||||
| public User User => _client.Users[UserId]; | public User User => _client.Users[UserId]; | ||||
| /// <summary> Returns the author of this message. </summary> | /// <summary> Returns the author of this message. </summary> | ||||
| [JsonIgnore] | [JsonIgnore] | ||||
| public Member Member | |||||
| { | |||||
| get | |||||
| { | |||||
| if (!Channel.IsPrivate) | |||||
| return _client.Members[UserId, ServerId]; | |||||
| else | |||||
| throw new InvalidOperationException("Unable to access Member in a private channel. Use User instead or check for Channel.IsPrivate."); | |||||
| } | |||||
| } | |||||
| public Member Member => _client.Members[UserId, ServerId]; | |||||
| internal Message(DiscordClient client, string id, string channelId, string userId) | internal Message(DiscordClient client, string id, string channelId, string userId) | ||||
| { | { | ||||
| @@ -174,6 +166,28 @@ namespace Discord | |||||
| Attachments = _initialAttachments; | Attachments = _initialAttachments; | ||||
| Embeds = _initialEmbeds; | Embeds = _initialEmbeds; | ||||
| MentionIds = _initialMentions; | MentionIds = _initialMentions; | ||||
| } | |||||
| internal void OnCached() | |||||
| { | |||||
| var channel = Channel; | |||||
| if (channel != null) | |||||
| channel.AddMessage(Id); | |||||
| var user = User; | |||||
| if (user != null) | |||||
| { | |||||
| user.AddRef(); | |||||
| _gotRef = true; | |||||
| } | |||||
| } | |||||
| internal void OnUncached() | |||||
| { | |||||
| var channel = Channel; | |||||
| if (channel != null) | |||||
| channel.RemoveMessage(Id); | |||||
| var user = User; | |||||
| if (user != null && _gotRef) | |||||
| user.RemoveRef(); | |||||
| _gotRef = false; | |||||
| } | } | ||||
| internal void Update(MessageInfo model) | internal void Update(MessageInfo model) | ||||
| @@ -52,7 +52,19 @@ namespace Discord | |||||
| if (IsEveryone) | if (IsEveryone) | ||||
| Position = int.MinValue; | Position = int.MinValue; | ||||
| } | |||||
| } | |||||
| internal void OnCached() | |||||
| { | |||||
| var server = Server; | |||||
| if (server != null) | |||||
| server.AddRole(Id); | |||||
| } | |||||
| internal void OnUncached() | |||||
| { | |||||
| var server = Server; | |||||
| if (server != null) | |||||
| server.RemoveRole(Id); | |||||
| } | |||||
| internal void Update(RoleInfo model) | internal void Update(RoleInfo model) | ||||
| { | { | ||||
| @@ -104,6 +104,23 @@ namespace Discord | |||||
| _members = new ConcurrentDictionary<string, bool>(); | _members = new ConcurrentDictionary<string, bool>(); | ||||
| _roles = new ConcurrentDictionary<string, bool>(); | _roles = new ConcurrentDictionary<string, bool>(); | ||||
| } | } | ||||
| internal void OnCached() | |||||
| { | |||||
| } | |||||
| internal void OnUncached() | |||||
| { | |||||
| var channels = _client.Channels; | |||||
| foreach (var channelId in ChannelIds) | |||||
| channels.TryRemove(channelId); | |||||
| var members = _client.Members; | |||||
| foreach (var userId in UserIds) | |||||
| members.TryRemove(userId, Id); | |||||
| var roles = _client.Roles; | |||||
| foreach (var roleId in RoleIds) | |||||
| roles.TryRemove(roleId); | |||||
| } | |||||
| internal void Update(GuildInfo model) | internal void Update(GuildInfo model) | ||||
| { | { | ||||
| @@ -80,7 +80,13 @@ namespace Discord | |||||
| _client = client; | _client = client; | ||||
| Id = id; | Id = id; | ||||
| _servers = new ConcurrentDictionary<string, bool>(); | _servers = new ConcurrentDictionary<string, bool>(); | ||||
| } | |||||
| } | |||||
| internal void OnCached() | |||||
| { | |||||
| } | |||||
| internal void OnUncached() | |||||
| { | |||||
| } | |||||
| internal void Update(UserReference model) | internal void Update(UserReference model) | ||||
| { | { | ||||