| @@ -551,6 +551,23 @@ namespace Discord.API | |||||
| await SendAsync("DELETE", $"channels/{channelId}/pins/{messageId}", options: options).ConfigureAwait(false); | await SendAsync("DELETE", $"channels/{channelId}/pins/{messageId}", options: options).ConfigureAwait(false); | ||||
| } | } | ||||
| //Channel Recipients | |||||
| public async Task AddGroupRecipientAsync(ulong channelId, ulong userId, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.GreaterThan(channelId, 0, nameof(channelId)); | |||||
| Preconditions.GreaterThan(userId, 0, nameof(userId)); | |||||
| await SendAsync("PUT", $"channels/{channelId}/recipients/{userId}", options: options).ConfigureAwait(false); | |||||
| } | |||||
| public async Task RemoveGroupRecipientAsync(ulong channelId, ulong userId, RequestOptions options = null) | |||||
| { | |||||
| Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||||
| Preconditions.NotEqual(userId, 0, nameof(userId)); | |||||
| await SendAsync("DELETE", $"channels/{channelId}/recipients/{userId}", options: options).ConfigureAwait(false); | |||||
| } | |||||
| //Guilds | //Guilds | ||||
| public async Task<Guild> GetGuildAsync(ulong guildId, RequestOptions options = null) | public async Task<Guild> GetGuildAsync(ulong guildId, RequestOptions options = null) | ||||
| { | { | ||||
| @@ -0,0 +1,12 @@ | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Gateway | |||||
| { | |||||
| public class RecipientEvent | |||||
| { | |||||
| [JsonProperty("user")] | |||||
| public User User { get; set; } | |||||
| [JsonProperty("channel_id")] | |||||
| public ulong ChannelId { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -170,11 +170,9 @@ namespace Discord | |||||
| return new DMChannel(this, new User(model.Recipients.Value[0]), model); | return new DMChannel(this, new User(model.Recipients.Value[0]), model); | ||||
| else if (model.Type == ChannelType.Group) | else if (model.Type == ChannelType.Group) | ||||
| { | { | ||||
| var recipients = model.Recipients.Value; | |||||
| var users = new ConcurrentDictionary<ulong, IUser>(1, recipients.Length); | |||||
| for (int i = 0; i < recipients.Length; i++) | |||||
| users[recipients[i].Id] = new User(recipients[i]); | |||||
| return new GroupChannel(this, users, model); | |||||
| var channel = new GroupChannel(this, model); | |||||
| channel.UpdateUsers(model.Recipients.Value, UpdateSource.Creation); | |||||
| return channel; | |||||
| } | } | ||||
| else | else | ||||
| throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); | throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); | ||||
| @@ -185,6 +185,18 @@ namespace Discord | |||||
| remove { _userIsTypingEvent.Remove(value); } | remove { _userIsTypingEvent.Remove(value); } | ||||
| } | } | ||||
| private readonly AsyncEvent<Func<IUser, IChannel, Task>> _userIsTypingEvent = new AsyncEvent<Func<IUser, IChannel, Task>>(); | private readonly AsyncEvent<Func<IUser, IChannel, Task>> _userIsTypingEvent = new AsyncEvent<Func<IUser, IChannel, Task>>(); | ||||
| public event Func<IGroupUser, Task> RecipientAdded | |||||
| { | |||||
| add { _recipientAddedEvent.Add(value); } | |||||
| remove { _recipientAddedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<IGroupUser, Task>> _recipientAddedEvent = new AsyncEvent<Func<IGroupUser, Task>>(); | |||||
| public event Func<IGroupUser, Task> RecipientRemoved | |||||
| { | |||||
| add { _recipientRemovedEvent.Add(value); } | |||||
| remove { _recipientRemovedEvent.Remove(value); } | |||||
| } | |||||
| private readonly AsyncEvent<Func<IGroupUser, Task>> _recipientRemovedEvent = new AsyncEvent<Func<IGroupUser, Task>>(); | |||||
| //TODO: Add PresenceUpdated? VoiceStateUpdated?, VoiceConnected, VoiceDisconnected; | //TODO: Add PresenceUpdated? VoiceStateUpdated?, VoiceConnected, VoiceDisconnected; | ||||
| } | } | ||||
| @@ -147,6 +147,7 @@ namespace Discord | |||||
| ConnectionState = ConnectionState.Connecting; | ConnectionState = ConnectionState.Connecting; | ||||
| await _gatewayLogger.InfoAsync("Connecting").ConfigureAwait(false); | await _gatewayLogger.InfoAsync("Connecting").ConfigureAwait(false); | ||||
| try | try | ||||
| { | { | ||||
| _connectTask = new TaskCompletionSource<bool>(); | _connectTask = new TaskCompletionSource<bool>(); | ||||
| @@ -160,7 +161,6 @@ namespace Discord | |||||
| await ApiClient.SendIdentifyAsync().ConfigureAwait(false); | await ApiClient.SendIdentifyAsync().ConfigureAwait(false); | ||||
| await _connectTask.Task.ConfigureAwait(false); | await _connectTask.Task.ConfigureAwait(false); | ||||
| ConnectionState = ConnectionState.Connected; | ConnectionState = ConnectionState.Connected; | ||||
| await _gatewayLogger.InfoAsync("Connected").ConfigureAwait(false); | await _gatewayLogger.InfoAsync("Connected").ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -173,6 +173,7 @@ namespace Discord | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public async Task DisconnectAsync() | public async Task DisconnectAsync() | ||||
| { | { | ||||
| if (_connectTask?.TrySetCanceled() ?? false) return; | |||||
| await _connectionLock.WaitAsync().ConfigureAwait(false); | await _connectionLock.WaitAsync().ConfigureAwait(false); | ||||
| try | try | ||||
| { | { | ||||
| @@ -181,6 +182,17 @@ namespace Discord | |||||
| } | } | ||||
| finally { _connectionLock.Release(); } | finally { _connectionLock.Release(); } | ||||
| } | } | ||||
| private async Task DisconnectAsync(Exception ex) | |||||
| { | |||||
| if (_connectTask?.TrySetException(ex) ?? false) return; | |||||
| await _connectionLock.WaitAsync().ConfigureAwait(false); | |||||
| try | |||||
| { | |||||
| _isReconnecting = false; | |||||
| await DisconnectInternalAsync(ex).ConfigureAwait(false); | |||||
| } | |||||
| finally { _connectionLock.Release(); } | |||||
| } | |||||
| private async Task DisconnectInternalAsync(Exception ex) | private async Task DisconnectInternalAsync(Exception ex) | ||||
| { | { | ||||
| ulong guildId; | ulong guildId; | ||||
| @@ -340,17 +352,14 @@ namespace Discord | |||||
| { | { | ||||
| var recipients = model.Recipients.Value; | var recipients = model.Recipients.Value; | ||||
| var user = GetOrAddUser(recipients[0], dataStore); | var user = GetOrAddUser(recipients[0], dataStore); | ||||
| var channel = new CachedDMChannel(this, new CachedPrivateUser(user), model); | |||||
| var channel = new CachedDMChannel(this, new CachedDMUser(user), model); | |||||
| dataStore.AddChannel(channel); | dataStore.AddChannel(channel); | ||||
| return channel; | return channel; | ||||
| } | } | ||||
| case ChannelType.Group: | case ChannelType.Group: | ||||
| { | { | ||||
| var recipients = model.Recipients.Value; | |||||
| var users = new ConcurrentDictionary<ulong, IUser>(1, recipients.Length); | |||||
| for (int i = 0; i < recipients.Length; i++) | |||||
| users[recipients[i].Id] = new CachedPrivateUser(GetOrAddUser(recipients[i], dataStore)); | |||||
| var channel = new CachedGroupChannel(this, users, model); | |||||
| var channel = new CachedGroupChannel(this, model); | |||||
| channel.UpdateUsers(model.Recipients.Value, UpdateSource.Creation); | |||||
| dataStore.AddChannel(channel); | dataStore.AddChannel(channel); | ||||
| return channel; | return channel; | ||||
| } | } | ||||
| @@ -521,34 +530,43 @@ namespace Discord | |||||
| //Connection | //Connection | ||||
| case "READY": | case "READY": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); | |||||
| var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); | |||||
| var dataStore = new DataStore( data.Guilds.Length, data.PrivateChannels.Length); | |||||
| try | |||||
| { | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); | |||||
| var data = (payload as JToken).ToObject<ReadyEvent>(_serializer); | |||||
| var dataStore = new DataStore(data.Guilds.Length, data.PrivateChannels.Length); | |||||
| var currentUser = new CachedSelfUser(this, data.User); | |||||
| int unavailableGuilds = 0; | |||||
| for (int i = 0; i < data.Guilds.Length; i++) | |||||
| var currentUser = new CachedSelfUser(this, data.User); | |||||
| int unavailableGuilds = 0; | |||||
| for (int i = 0; i < data.Guilds.Length; i++) | |||||
| { | |||||
| var model = data.Guilds[i]; | |||||
| AddGuild(model, dataStore); | |||||
| if (model.Unavailable == true) | |||||
| unavailableGuilds++; | |||||
| } | |||||
| for (int i = 0; i < data.PrivateChannels.Length; i++) | |||||
| AddPrivateChannel(data.PrivateChannels[i], dataStore); | |||||
| _sessionId = data.SessionId; | |||||
| _currentUser = currentUser; | |||||
| _unavailableGuilds = unavailableGuilds; | |||||
| _lastGuildAvailableTime = Environment.TickCount; | |||||
| DataStore = dataStore; | |||||
| } | |||||
| catch (Exception ex) | |||||
| { | { | ||||
| var model = data.Guilds[i]; | |||||
| AddGuild(model, dataStore); | |||||
| if (model.Unavailable == true) | |||||
| unavailableGuilds++; | |||||
| await DisconnectAsync(new Exception("Processing READY failed", ex)); | |||||
| return; | |||||
| } | } | ||||
| for (int i = 0; i < data.PrivateChannels.Length; i++) | |||||
| AddPrivateChannel(data.PrivateChannels[i], dataStore); | |||||
| _sessionId = data.SessionId; | |||||
| _currentUser = currentUser; | |||||
| _unavailableGuilds = unavailableGuilds; | |||||
| _lastGuildAvailableTime = Environment.TickCount; | |||||
| DataStore = dataStore; | |||||
| _guildDownloadTask = WaitForGuildsAsync(_cancelToken.Token, _clientLogger); | _guildDownloadTask = WaitForGuildsAsync(_cancelToken.Token, _clientLogger); | ||||
| await _readyEvent.InvokeAsync().ConfigureAwait(false); | await _readyEvent.InvokeAsync().ConfigureAwait(false); | ||||
| await SyncGuildsAsync().ConfigureAwait(false); | await SyncGuildsAsync().ConfigureAwait(false); | ||||
| var _ = _connectTask.TrySetResultAsync(true); //Signal the .Connect() call to complete | var _ = _connectTask.TrySetResultAsync(true); //Signal the .Connect() call to complete | ||||
| await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); | await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -913,6 +931,51 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| break; | break; | ||||
| case "CHANNEL_RECIPIENT_ADD": | |||||
| { | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_ADD)").ConfigureAwait(false); | |||||
| var data = (payload as JToken).ToObject<RecipientEvent>(_serializer); | |||||
| var channel = DataStore.GetChannel(data.ChannelId) as CachedGroupChannel; | |||||
| if (channel != null) | |||||
| { | |||||
| var user = channel.AddUser(data.User, DataStore); | |||||
| await _recipientAddedEvent.InvokeAsync(user).ConfigureAwait(false); | |||||
| } | |||||
| else | |||||
| { | |||||
| await _gatewayLogger.WarningAsync("CHANNEL_RECIPIENT_ADD referenced an unknown channel.").ConfigureAwait(false); | |||||
| return; | |||||
| } | |||||
| } | |||||
| break; | |||||
| case "CHANNEL_RECIPIENT_REMOVE": | |||||
| { | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_REMOVE)").ConfigureAwait(false); | |||||
| var data = (payload as JToken).ToObject<RecipientEvent>(_serializer); | |||||
| var channel = DataStore.GetChannel(data.ChannelId) as CachedGroupChannel; | |||||
| if (channel != null) | |||||
| { | |||||
| var user = channel.RemoveUser(data.User.Id); | |||||
| if (user != null) | |||||
| { | |||||
| user.User.RemoveRef(this); | |||||
| await _recipientRemovedEvent.InvokeAsync(user).ConfigureAwait(false); | |||||
| } | |||||
| else | |||||
| { | |||||
| await _gatewayLogger.WarningAsync("CHANNEL_RECIPIENT_REMOVE referenced an unknown user.").ConfigureAwait(false); | |||||
| return; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| await _gatewayLogger.WarningAsync("CHANNEL_RECIPIENT_ADD referenced an unknown channel.").ConfigureAwait(false); | |||||
| return; | |||||
| } | |||||
| } | |||||
| break; | |||||
| //Roles | //Roles | ||||
| case "GUILD_ROLE_CREATE": | case "GUILD_ROLE_CREATE": | ||||
| @@ -15,7 +15,7 @@ namespace Discord | |||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| internal class GroupChannel : SnowflakeEntity, IGroupChannel | internal class GroupChannel : SnowflakeEntity, IGroupChannel | ||||
| { | { | ||||
| protected ConcurrentDictionary<ulong, IUser> _users; | |||||
| protected ConcurrentDictionary<ulong, GroupUser> _users; | |||||
| private string _iconId; | private string _iconId; | ||||
| public override DiscordClient Discord { get; } | public override DiscordClient Discord { get; } | ||||
| @@ -25,11 +25,10 @@ namespace Discord | |||||
| public virtual IReadOnlyCollection<IMessage> CachedMessages => ImmutableArray.Create<IMessage>(); | public virtual IReadOnlyCollection<IMessage> CachedMessages => ImmutableArray.Create<IMessage>(); | ||||
| public string IconUrl => API.CDN.GetChannelIconUrl(Id, _iconId); | public string IconUrl => API.CDN.GetChannelIconUrl(Id, _iconId); | ||||
| public GroupChannel(DiscordClient discord, ConcurrentDictionary<ulong, IUser> recipients, Model model) | |||||
| public GroupChannel(DiscordClient discord, Model model) | |||||
| : base(model.Id) | : base(model.Id) | ||||
| { | { | ||||
| Discord = discord; | Discord = discord; | ||||
| _users = recipients; | |||||
| Update(model, UpdateSource.Creation); | Update(model, UpdateSource.Creation); | ||||
| } | } | ||||
| @@ -46,13 +45,13 @@ namespace Discord | |||||
| UpdateUsers(model.Recipients.Value, source); | UpdateUsers(model.Recipients.Value, source); | ||||
| } | } | ||||
| protected virtual void UpdateUsers(API.User[] models, UpdateSource source) | |||||
| internal virtual void UpdateUsers(API.User[] models, UpdateSource source) | |||||
| { | { | ||||
| if (!IsAttached) | if (!IsAttached) | ||||
| { | { | ||||
| var users = new ConcurrentDictionary<ulong, IUser>(1, (int)(models.Length * 1.05)); | |||||
| var users = new ConcurrentDictionary<ulong, GroupUser>(1, (int)(models.Length * 1.05)); | |||||
| for (int i = 0; i < models.Length; i++) | for (int i = 0; i < models.Length; i++) | ||||
| users[models[i].Id] = new User(models[i]); | |||||
| users[models[i].Id] = new GroupUser(this, new User(models[i])); | |||||
| _users = users; | _users = users; | ||||
| } | } | ||||
| } | } | ||||
| @@ -69,9 +68,13 @@ namespace Discord | |||||
| await Discord.ApiClient.DeleteChannelAsync(Id).ConfigureAwait(false); | await Discord.ApiClient.DeleteChannelAsync(Id).ConfigureAwait(false); | ||||
| } | } | ||||
| public async Task AddUserAsync(IUser user) | |||||
| { | |||||
| await Discord.ApiClient.AddGroupRecipientAsync(Id, user.Id).ConfigureAwait(false); | |||||
| } | |||||
| public async Task<IUser> GetUserAsync(ulong id) | public async Task<IUser> GetUserAsync(ulong id) | ||||
| { | { | ||||
| IUser user; | |||||
| GroupUser user; | |||||
| if (_users.TryGetValue(id, out user)) | if (_users.TryGetValue(id, out user)) | ||||
| return user; | return user; | ||||
| var currentUser = await Discord.GetCurrentUserAsync().ConfigureAwait(false); | var currentUser = await Discord.GetCurrentUserAsync().ConfigureAwait(false); | ||||
| @@ -82,7 +85,7 @@ namespace Discord | |||||
| public async Task<IReadOnlyCollection<IUser>> GetUsersAsync() | public async Task<IReadOnlyCollection<IUser>> GetUsersAsync() | ||||
| { | { | ||||
| var currentUser = await Discord.GetCurrentUserAsync().ConfigureAwait(false); | var currentUser = await Discord.GetCurrentUserAsync().ConfigureAwait(false); | ||||
| return _users.Select(x => x.Value).Concat(ImmutableArray.Create(currentUser)).ToReadOnlyCollection(_users); | |||||
| return _users.Select(x => x.Value).Concat<IUser>(ImmutableArray.Create(currentUser)).ToReadOnlyCollection(_users); | |||||
| } | } | ||||
| public async Task<IMessage> SendMessageAsync(string text, bool isTTS) | public async Task<IMessage> SendMessageAsync(string text, bool isTTS) | ||||
| @@ -4,6 +4,9 @@ namespace Discord | |||||
| { | { | ||||
| public interface IGroupChannel : IMessageChannel, IPrivateChannel | public interface IGroupChannel : IMessageChannel, IPrivateChannel | ||||
| { | { | ||||
| /// <summary> Adds a user to this group. </summary> | |||||
| Task AddUserAsync(IUser user); | |||||
| /// <summary> Leaves this group. </summary> | /// <summary> Leaves this group. </summary> | ||||
| Task LeaveAsync(); | Task LeaveAsync(); | ||||
| } | } | ||||
| @@ -0,0 +1,47 @@ | |||||
| using Discord.API.Rest; | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| internal class GroupUser : IGroupUser | |||||
| { | |||||
| public GroupChannel Channel { get; private set; } | |||||
| public User User { get; private set; } | |||||
| public ulong Id => User.Id; | |||||
| public string AvatarUrl => User.AvatarUrl; | |||||
| public DateTimeOffset CreatedAt => User.CreatedAt; | |||||
| public string Discriminator => User.Discriminator; | |||||
| public ushort DiscriminatorValue => User.DiscriminatorValue; | |||||
| public bool IsAttached => User.IsAttached; | |||||
| public bool IsBot => User.IsBot; | |||||
| public string Mention => User.Mention; | |||||
| public string NicknameMention => User.NicknameMention; | |||||
| public string Username => User.Username; | |||||
| public virtual UserStatus Status => UserStatus.Unknown; | |||||
| public virtual Game Game => null; | |||||
| public DiscordClient Discord => Channel.Discord; | |||||
| public GroupUser(GroupChannel channel, User user) | |||||
| { | |||||
| Channel = channel; | |||||
| User = user; | |||||
| } | |||||
| public async Task KickAsync() | |||||
| { | |||||
| await Discord.ApiClient.RemoveGroupRecipientAsync(Channel.Id, Id).ConfigureAwait(false); | |||||
| } | |||||
| public async Task<IDMChannel> CreateDMChannelAsync() | |||||
| { | |||||
| var args = new CreateDMChannelParams { Recipient = this }; | |||||
| var model = await Discord.ApiClient.CreateDMChannelAsync(args).ConfigureAwait(false); | |||||
| return new DMChannel(Discord, new User(model.Recipients.Value[0]), model); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -7,7 +7,6 @@ using System.Linq; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Model = Discord.API.GuildMember; | using Model = Discord.API.GuildMember; | ||||
| using PresenceModel = Discord.API.Presence; | using PresenceModel = Discord.API.Presence; | ||||
| using VoiceStateModel = Discord.API.VoiceState; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| @@ -0,0 +1,13 @@ | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| public interface IGroupUser : IUser | |||||
| { | |||||
| /// <summary> Kicks this user from this group. </summary> | |||||
| Task KickAsync(); | |||||
| /// <summary> Returns a private message channel to this user, creating one if it does not already exist. </summary> | |||||
| Task<IDMChannel> CreateDMChannelAsync(); | |||||
| } | |||||
| } | |||||
| @@ -11,11 +11,11 @@ namespace Discord | |||||
| private readonly MessageManager _messages; | private readonly MessageManager _messages; | ||||
| public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | ||||
| public new CachedPrivateUser Recipient => base.Recipient as CachedPrivateUser; | |||||
| public new CachedDMUser Recipient => base.Recipient as CachedDMUser; | |||||
| public IReadOnlyCollection<ICachedUser> Members => ImmutableArray.Create<ICachedUser>(Discord.CurrentUser, Recipient); | public IReadOnlyCollection<ICachedUser> Members => ImmutableArray.Create<ICachedUser>(Discord.CurrentUser, Recipient); | ||||
| IReadOnlyCollection<CachedPrivateUser> ICachedPrivateChannel.Recipients => ImmutableArray.Create(Recipient); | |||||
| IReadOnlyCollection<CachedDMUser> ICachedPrivateChannel.Recipients => ImmutableArray.Create(Recipient); | |||||
| public CachedDMChannel(DiscordSocketClient discord, CachedPrivateUser recipient, Model model) | |||||
| public CachedDMChannel(DiscordSocketClient discord, CachedDMUser recipient, Model model) | |||||
| : base(discord, recipient, model) | : base(discord, recipient, model) | ||||
| { | { | ||||
| if (Discord.MessageCacheSize > 0) | if (Discord.MessageCacheSize > 0) | ||||
| @@ -5,7 +5,7 @@ using PresenceModel = Discord.API.Presence; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| [DebuggerDisplay("{DebuggerDisplay,nq}")] | [DebuggerDisplay("{DebuggerDisplay,nq}")] | ||||
| internal class CachedPrivateUser : ICachedUser | |||||
| internal class CachedDMUser : ICachedUser | |||||
| { | { | ||||
| public CachedGlobalUser User { get; } | public CachedGlobalUser User { get; } | ||||
| @@ -26,7 +26,7 @@ namespace Discord | |||||
| public string NicknameMention => User.NicknameMention; | public string NicknameMention => User.NicknameMention; | ||||
| public string Username => User.Username; | public string Username => User.Username; | ||||
| public CachedPrivateUser(CachedGlobalUser user) | |||||
| public CachedDMUser(CachedGlobalUser user) | |||||
| { | { | ||||
| User = user; | User = user; | ||||
| } | } | ||||
| @@ -36,7 +36,7 @@ namespace Discord | |||||
| User.Update(model, source); | User.Update(model, source); | ||||
| } | } | ||||
| public CachedPrivateUser Clone() => MemberwiseClone() as CachedPrivateUser; | |||||
| public CachedDMUser Clone() => MemberwiseClone() as CachedDMUser; | |||||
| ICachedUser ICachedUser.Clone() => Clone(); | ICachedUser ICachedUser.Clone() => Clone(); | ||||
| public override string ToString() => $"{Username}#{Discriminator}"; | public override string ToString() => $"{Username}#{Discriminator}"; | ||||
| @@ -6,6 +6,7 @@ using System.Linq; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using MessageModel = Discord.API.Message; | using MessageModel = Discord.API.Message; | ||||
| using Model = Discord.API.Channel; | using Model = Discord.API.Channel; | ||||
| using UserModel = Discord.API.User; | |||||
| using VoiceStateModel = Discord.API.VoiceState; | using VoiceStateModel = Discord.API.VoiceState; | ||||
| namespace Discord | namespace Discord | ||||
| @@ -17,11 +18,11 @@ namespace Discord | |||||
| public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | ||||
| public IReadOnlyCollection<ICachedUser> Members | public IReadOnlyCollection<ICachedUser> Members | ||||
| => _users.Select(x => x.Value).Concat(ImmutableArray.Create(Discord.CurrentUser)).Cast<ICachedUser>().ToReadOnlyCollection(() => _users.Count + 1); | |||||
| public new IReadOnlyCollection<CachedPrivateUser> Recipients => _users.Cast<CachedPrivateUser>().ToReadOnlyCollection(_users); | |||||
| => _users.Select(x => x.Value as ICachedUser).Concat(ImmutableArray.Create(Discord.CurrentUser)).ToReadOnlyCollection(() => _users.Count + 1); | |||||
| public new IReadOnlyCollection<CachedDMUser> Recipients => _users.Cast<CachedDMUser>().ToReadOnlyCollection(_users); | |||||
| public CachedGroupChannel(DiscordSocketClient discord, ConcurrentDictionary<ulong, IUser> recipients, Model model) | |||||
| : base(discord, recipients, model) | |||||
| public CachedGroupChannel(DiscordSocketClient discord, Model model) | |||||
| : base(discord, model) | |||||
| { | { | ||||
| if (Discord.MessageCacheSize > 0) | if (Discord.MessageCacheSize > 0) | ||||
| _messages = new MessageCache(Discord, this); | _messages = new MessageCache(Discord, this); | ||||
| @@ -36,23 +37,46 @@ namespace Discord | |||||
| base.Update(model, source); | base.Update(model, source); | ||||
| } | } | ||||
| protected override void UpdateUsers(API.User[] models, UpdateSource source) | |||||
| internal override void UpdateUsers(UserModel[] models, UpdateSource source) | |||||
| { | { | ||||
| var users = new ConcurrentDictionary<ulong, IUser>(1, models.Length); | |||||
| var users = new ConcurrentDictionary<ulong, GroupUser>(1, models.Length); | |||||
| for (int i = 0; i < models.Length; i++) | for (int i = 0; i < models.Length; i++) | ||||
| users[models[i].Id] = new CachedPrivateUser(Discord.GetOrAddUser(models[i], Discord.DataStore)); | |||||
| { | |||||
| var globalUser = Discord.GetOrAddUser(models[i], Discord.DataStore); | |||||
| users[models[i].Id] = new CachedGroupUser(this, globalUser); | |||||
| } | |||||
| _users = users; | _users = users; | ||||
| } | } | ||||
| public CachedGroupUser AddUser(UserModel model, DataStore dataStore) | |||||
| { | |||||
| GroupUser user; | |||||
| if (_users.TryGetValue(model.Id, out user)) | |||||
| return user as CachedGroupUser; | |||||
| else | |||||
| { | |||||
| var globalUser = Discord.GetOrAddUser(model, dataStore); | |||||
| var privateUser = new CachedGroupUser(this, globalUser); | |||||
| _users[privateUser.Id] = privateUser; | |||||
| return privateUser; | |||||
| } | |||||
| } | |||||
| public ICachedUser GetUser(ulong id) | public ICachedUser GetUser(ulong id) | ||||
| { | { | ||||
| IUser user; | |||||
| GroupUser user; | |||||
| if (_users.TryGetValue(id, out user)) | if (_users.TryGetValue(id, out user)) | ||||
| return user as ICachedUser; | |||||
| return user as CachedGroupUser; | |||||
| if (id == Discord.CurrentUser.Id) | if (id == Discord.CurrentUser.Id) | ||||
| return Discord.CurrentUser; | return Discord.CurrentUser; | ||||
| return null; | return null; | ||||
| } | } | ||||
| public CachedGroupUser RemoveUser(ulong id) | |||||
| { | |||||
| GroupUser user; | |||||
| if (_users.TryRemove(id, out user)) | |||||
| return user as CachedGroupUser; | |||||
| return null; | |||||
| } | |||||
| public VoiceState AddOrUpdateVoiceState(VoiceStateModel model, DataStore dataStore, ConcurrentDictionary<ulong, VoiceState> voiceStates = null) | public VoiceState AddOrUpdateVoiceState(VoiceStateModel model, DataStore dataStore, ConcurrentDictionary<ulong, VoiceState> voiceStates = null) | ||||
| { | { | ||||
| @@ -106,15 +130,7 @@ namespace Discord | |||||
| public CachedDMChannel Clone() => MemberwiseClone() as CachedDMChannel; | public CachedDMChannel Clone() => MemberwiseClone() as CachedDMChannel; | ||||
| IMessage IMessageChannel.GetCachedMessage(ulong id) => GetMessage(id); | IMessage IMessageChannel.GetCachedMessage(ulong id) => GetMessage(id); | ||||
| ICachedUser ICachedMessageChannel.GetUser(ulong id, bool skipCheck) | |||||
| { | |||||
| IUser user; | |||||
| if (_users.TryGetValue(id, out user)) | |||||
| return user as ICachedUser; | |||||
| if (id == Discord.CurrentUser.Id) | |||||
| return Discord.CurrentUser; | |||||
| return null; | |||||
| } | |||||
| ICachedUser ICachedMessageChannel.GetUser(ulong id, bool skipCheck) => GetUser(id); | |||||
| ICachedChannel ICachedChannel.Clone() => Clone(); | ICachedChannel ICachedChannel.Clone() => Clone(); | ||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,33 @@ | |||||
| using System.Diagnostics; | |||||
| namespace Discord | |||||
| { | |||||
| [DebuggerDisplay("{DebuggerDisplay,nq}")] | |||||
| internal class CachedGroupUser : GroupUser, ICachedUser | |||||
| { | |||||
| public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | |||||
| public new CachedGroupChannel Channel => base.Channel as CachedGroupChannel; | |||||
| public new CachedGlobalUser User => base.User as CachedGlobalUser; | |||||
| public Presence Presence => User.Presence; //{ get; private set; } | |||||
| public override Game Game => Presence.Game; | |||||
| public override UserStatus Status => Presence.Status; | |||||
| public VoiceState? VoiceState => Channel.GetVoiceState(Id); | |||||
| public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false; | |||||
| public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false; | |||||
| public bool IsSuppressed => VoiceState?.IsSuppressed ?? false; | |||||
| public CachedVoiceChannel VoiceChannel => VoiceState?.VoiceChannel; | |||||
| public CachedGroupUser(CachedGroupChannel channel, CachedGlobalUser user) | |||||
| : base(channel, user) | |||||
| { | |||||
| } | |||||
| public CachedGroupUser Clone() => MemberwiseClone() as CachedGroupUser; | |||||
| ICachedUser ICachedUser.Clone() => Clone(); | |||||
| public override string ToString() => $"{Username}#{Discriminator}"; | |||||
| private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id})"; | |||||
| } | |||||
| } | |||||
| @@ -208,7 +208,6 @@ namespace Discord | |||||
| var user = Discord.GetOrAddUser(model.User, dataStore); | var user = Discord.GetOrAddUser(model.User, dataStore); | ||||
| member = new CachedGuildUser(this, user, model); | member = new CachedGuildUser(this, user, model); | ||||
| members[user.Id] = member; | members[user.Id] = member; | ||||
| user.AddRef(); | |||||
| DownloadedMemberCount++; | DownloadedMemberCount++; | ||||
| } | } | ||||
| return member; | return member; | ||||
| @@ -4,6 +4,6 @@ namespace Discord | |||||
| { | { | ||||
| internal interface ICachedPrivateChannel : ICachedChannel, IPrivateChannel | internal interface ICachedPrivateChannel : ICachedChannel, IPrivateChannel | ||||
| { | { | ||||
| new IReadOnlyCollection<CachedPrivateUser> Recipients { get; } | |||||
| new IReadOnlyCollection<CachedDMUser> Recipients { get; } | |||||
| } | } | ||||
| } | } | ||||