@@ -551,6 +551,23 @@ namespace Discord.API | |||
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 | |||
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); | |||
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 | |||
throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); | |||
@@ -185,6 +185,18 @@ namespace Discord | |||
remove { _userIsTypingEvent.Remove(value); } | |||
} | |||
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; | |||
} | |||
@@ -147,6 +147,7 @@ namespace Discord | |||
ConnectionState = ConnectionState.Connecting; | |||
await _gatewayLogger.InfoAsync("Connecting").ConfigureAwait(false); | |||
try | |||
{ | |||
_connectTask = new TaskCompletionSource<bool>(); | |||
@@ -160,7 +161,6 @@ namespace Discord | |||
await ApiClient.SendIdentifyAsync().ConfigureAwait(false); | |||
await _connectTask.Task.ConfigureAwait(false); | |||
ConnectionState = ConnectionState.Connected; | |||
await _gatewayLogger.InfoAsync("Connected").ConfigureAwait(false); | |||
} | |||
@@ -173,6 +173,7 @@ namespace Discord | |||
/// <inheritdoc /> | |||
public async Task DisconnectAsync() | |||
{ | |||
if (_connectTask?.TrySetCanceled() ?? false) return; | |||
await _connectionLock.WaitAsync().ConfigureAwait(false); | |||
try | |||
{ | |||
@@ -181,6 +182,17 @@ namespace Discord | |||
} | |||
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) | |||
{ | |||
ulong guildId; | |||
@@ -340,17 +352,14 @@ namespace Discord | |||
{ | |||
var recipients = model.Recipients.Value; | |||
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); | |||
return channel; | |||
} | |||
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); | |||
return channel; | |||
} | |||
@@ -521,34 +530,43 @@ namespace Discord | |||
//Connection | |||
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); | |||
await _readyEvent.InvokeAsync().ConfigureAwait(false); | |||
await SyncGuildsAsync().ConfigureAwait(false); | |||
var _ = _connectTask.TrySetResultAsync(true); //Signal the .Connect() call to complete | |||
await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); | |||
} | |||
@@ -913,6 +931,51 @@ namespace Discord | |||
} | |||
} | |||
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 | |||
case "GUILD_ROLE_CREATE": | |||
@@ -15,7 +15,7 @@ namespace Discord | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
internal class GroupChannel : SnowflakeEntity, IGroupChannel | |||
{ | |||
protected ConcurrentDictionary<ulong, IUser> _users; | |||
protected ConcurrentDictionary<ulong, GroupUser> _users; | |||
private string _iconId; | |||
public override DiscordClient Discord { get; } | |||
@@ -25,11 +25,10 @@ namespace Discord | |||
public virtual IReadOnlyCollection<IMessage> CachedMessages => ImmutableArray.Create<IMessage>(); | |||
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) | |||
{ | |||
Discord = discord; | |||
_users = recipients; | |||
Update(model, UpdateSource.Creation); | |||
} | |||
@@ -46,13 +45,13 @@ namespace Discord | |||
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) | |||
{ | |||
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++) | |||
users[models[i].Id] = new User(models[i]); | |||
users[models[i].Id] = new GroupUser(this, new User(models[i])); | |||
_users = users; | |||
} | |||
} | |||
@@ -69,9 +68,13 @@ namespace Discord | |||
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) | |||
{ | |||
IUser user; | |||
GroupUser user; | |||
if (_users.TryGetValue(id, out user)) | |||
return user; | |||
var currentUser = await Discord.GetCurrentUserAsync().ConfigureAwait(false); | |||
@@ -82,7 +85,7 @@ namespace Discord | |||
public async Task<IReadOnlyCollection<IUser>> GetUsersAsync() | |||
{ | |||
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) | |||
@@ -4,6 +4,9 @@ namespace Discord | |||
{ | |||
public interface IGroupChannel : IMessageChannel, IPrivateChannel | |||
{ | |||
/// <summary> Adds a user to this group. </summary> | |||
Task AddUserAsync(IUser user); | |||
/// <summary> Leaves this group. </summary> | |||
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 Model = Discord.API.GuildMember; | |||
using PresenceModel = Discord.API.Presence; | |||
using VoiceStateModel = Discord.API.VoiceState; | |||
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; | |||
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); | |||
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) | |||
{ | |||
if (Discord.MessageCacheSize > 0) | |||
@@ -5,7 +5,7 @@ using PresenceModel = Discord.API.Presence; | |||
namespace Discord | |||
{ | |||
[DebuggerDisplay("{DebuggerDisplay,nq}")] | |||
internal class CachedPrivateUser : ICachedUser | |||
internal class CachedDMUser : ICachedUser | |||
{ | |||
public CachedGlobalUser User { get; } | |||
@@ -26,7 +26,7 @@ namespace Discord | |||
public string NicknameMention => User.NicknameMention; | |||
public string Username => User.Username; | |||
public CachedPrivateUser(CachedGlobalUser user) | |||
public CachedDMUser(CachedGlobalUser user) | |||
{ | |||
User = user; | |||
} | |||
@@ -36,7 +36,7 @@ namespace Discord | |||
User.Update(model, source); | |||
} | |||
public CachedPrivateUser Clone() => MemberwiseClone() as CachedPrivateUser; | |||
public CachedDMUser Clone() => MemberwiseClone() as CachedDMUser; | |||
ICachedUser ICachedUser.Clone() => Clone(); | |||
public override string ToString() => $"{Username}#{Discriminator}"; |
@@ -6,6 +6,7 @@ using System.Linq; | |||
using System.Threading.Tasks; | |||
using MessageModel = Discord.API.Message; | |||
using Model = Discord.API.Channel; | |||
using UserModel = Discord.API.User; | |||
using VoiceStateModel = Discord.API.VoiceState; | |||
namespace Discord | |||
@@ -17,11 +18,11 @@ namespace Discord | |||
public new DiscordSocketClient Discord => base.Discord as DiscordSocketClient; | |||
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) | |||
_messages = new MessageCache(Discord, this); | |||
@@ -36,23 +37,46 @@ namespace Discord | |||
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++) | |||
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; | |||
} | |||
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) | |||
{ | |||
IUser user; | |||
GroupUser user; | |||
if (_users.TryGetValue(id, out user)) | |||
return user as ICachedUser; | |||
return user as CachedGroupUser; | |||
if (id == Discord.CurrentUser.Id) | |||
return Discord.CurrentUser; | |||
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) | |||
{ | |||
@@ -106,15 +130,7 @@ namespace Discord | |||
public CachedDMChannel Clone() => MemberwiseClone() as CachedDMChannel; | |||
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(); | |||
} | |||
} |
@@ -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); | |||
member = new CachedGuildUser(this, user, model); | |||
members[user.Id] = member; | |||
user.AddRef(); | |||
DownloadedMemberCount++; | |||
} | |||
return member; | |||
@@ -4,6 +4,6 @@ namespace Discord | |||
{ | |||
internal interface ICachedPrivateChannel : ICachedChannel, IPrivateChannel | |||
{ | |||
new IReadOnlyCollection<CachedPrivateUser> Recipients { get; } | |||
new IReadOnlyCollection<CachedDMUser> Recipients { get; } | |||
} | |||
} |